1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
/*
* esodata - data structures and other things, of varying utility
* Copyright 2022, Ben Culkin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package bjc.functypes;
import java.util.*;
import java.util.function.*;
import bjc.typeclasses.Isomorphism;
/**
* An instance of {@link Function} that can throw an exception.
*
* @author Ben Culkin
*
* @param <InputType> The input to the function.
* @param <ReturnType> The output to the function.
* @param <ExType> The type of exception thrown.
*/
public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable>
{
/**
* Does the possibly throwing computation embodied by this function.
*
* @param arg The input to the function.
*
* @return The result of the function.
*
* @throws ExType If something went wrong with the function.
*/
public ReturnType apply(InputType arg) throws ExType;
/**
* Converts this into a {@link Function} by handling any thrown exceptions,
* then mapping the return type to get a consistent return type.
*
* @param <NewReturn> The new return type.
*
* @param clasz The class of the handled exception. This needs to be provided
* because you can't catch a generic exception, and we want to
* make sure that we aren't catching any types of exception that
* we aren't supposed to.
* @param mapper The function which maps the normal return.
* @param handler The handler to use.
*
* @return A function which will either return its proper value, or the result
* of invoking the handler.
*/
@SuppressWarnings("unchecked")
default <NewReturn> Function<InputType, NewReturn> swallowMap(
Class<ExType> clasz,
Function<ReturnType, NewReturn> mapper,
Function<ExType, NewReturn> handler)
{
return (inp) -> {
try {
return mapper.apply(this.apply(inp));
} catch (Throwable ex) {
if (clasz.isInstance(ex)) {
// Swallow this
return handler.apply((ExType) ex);
}
String msg = "Exception of incorrect type to be handled, only "
+ clasz.getName()
+ " are handled";
throw new RuntimeException(msg, ex);
}
};
}
/**
* Converts this into a {@link Function} by handling any thrown exceptions.
*
* @param clasz The class of the handled exception. This needs to be provided
* because you can't catch a generic exception, and we want to
* make sure that we aren't catching any types of exception that
* we aren't supposed to.
*
* @param handler The handler to use.
*
* @return A function which will either return its proper value, or the result
* of invoking the handler.
*/
default Function<InputType, ReturnType> swallow(
Class<ExType> clasz, Function<ExType, ReturnType> handler)
{
return swallowMap(clasz, (arg) -> arg, handler);
}
/**
* Convert this to a function which will attempt to recover from the thrown exception.
*
* @param clasz The class of the exception. Needed for type-safety reasons.
* @param rescue The function to use to convert an exception into a safe input value.
*
* @return A function which attempts to recover from a exception.
*/
@SuppressWarnings("unchecked")
default Function<InputType, ReturnType> recover(
Class<ExType> clasz, BiFunction<InputType, ExType, InputType> rescue)
{
return Fixpoints.fix((arg, self) -> {
try {
return this.apply(arg);
} catch (Throwable ex) {
if (clasz.isInstance(ex)) {
// Swallow this
return self.apply(rescue.apply(arg, (ExType) ex));
}
String msg = "Exception of incorrect type to be handled, only "
+ clasz.getName()
+ " are handled";
throw new RuntimeException(msg, ex);
}
});
}
/**
* Create a {@link Thrower} which will yield the result of calling this
* function with the given argument.
*
* @param arg The argument to use.
*
* @return A thrower which will call this function with the given value.
*/
default Thrower<ReturnType, ExType> suspend(InputType arg)
{
return () -> this.apply(arg);
}
/**
* Compose two throwing functions together.
*
* @param <NewOutput> The newly output type.
*
* @param func The throwing function to compose this with.
*
* @return A throwing function that composes the two.
*/
default <NewOutput> ThrowFunction<InputType, NewOutput, ExType>
compose(
ThrowFunction<ReturnType, NewOutput, ExType> func) {
return (arg) -> func.apply(this.apply(arg));
}
/** Convert this function into one which will return an empty optional if an
* exception is thrown, returning an optional containing the value otherwise.
*
* Note that if this function returns a null value by itself, that will also
* yield an empty nullable.
*
* @param clasz The class of the exception. Needed because of type erasure,
* to ensure that we are catching the proper class.
*
* @return A function which returns an optional instead.
*/
default Function<InputType, Optional<ReturnType>>
makeTotal(Class<ExType> clasz)
{
return swallowMap(clasz, Optional::ofNullable, (ignored) -> Optional.empty());
}
/**
* ThrowFunctions and functions which return a {@link Thrower} are isomorphic.
*
* @param <InpType> The function input type.
* @param <OutType> The function output type.
* @param <ExType> The exception type.
*
* @return The isomorphism between them.
*/
static <InpType, OutType, ExType extends Throwable>
Isomorphism<ThrowFunction<InpType, OutType, ExType>, Function<InpType, Thrower<OutType, ExType>>>
getIso()
{
// @FIXME Nov 23, 2020 Ben Culkin :EquivISO
// Fix this to strip wrappers when appropriate, so that going
// backwards and forwards leaves you where you started, not under
// two levels of indirection.
return Isomorphism.from((throwFun) -> {
return (arg) -> throwFun.suspend(arg);
}, (thrower) -> {
return (arg) -> thrower.apply(arg).extract();
});
}
}
|