-
Notifications
You must be signed in to change notification settings - Fork 2
/
OptExtensions.xtend
247 lines (207 loc) · 6.8 KB
/
OptExtensions.xtend
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package nl.kii.util
class OptExtensions {
// BOOLEAN DEFINED CHECK //////////////////////////////////////////////////
/**
* Checks if an object is defined, meaning here that it is not empty or faulty
*/
def static <T> defined(Object o) {
switch o {
case null: false
None<T>: false
Err<T>: false
default: true
}
}
/** Checks if an object is truthy, meaning it is not false and it is defined. */
def static <T> truthy(Object o) {
switch o {
Boolean: o
default: o.defined
}
}
/**
* Only perform the function for a given condition. Returns an optional result.
* <pre>val Opt<User> user = ifTrue(isMale) [ getUser ]</pre>
*/
def static <T> Opt<T> ifTrue(boolean condition, (Object)=>T fn) {
if(condition) fn.apply(null).option
else none
}
def static <T> Opt<T> => (Opt<T> o, (T)=>void fn) {
ifSome(o, fn)
}
/**
* Only perform the function if something was set. Returns the result of the function for chaining
*/
def static <T> Opt<T> ifSome(Opt<T> o, (T)=>void fn) {
if(o.defined) fn.apply(o.value)
o
}
/**
* Only perform the function if o is a None. Returns the result of the function for chaining
*/
def static <T> Opt<T> ifNone(Opt<T> o, =>void fn) {
if(o.hasNone) fn.apply
o
}
/**
* Only perform the function if the passed argument was empty,
* meaning null or empty or an error. Returns an optional result.
* <pre>val Opt<User> user = ifEmpty(userId) [ getDefaultUser ]</pre>
*/
def static <T> Opt<T> ifEmpty(Opt<T> o, =>void fn) {
if(!o.defined) fn.apply
o
}
/**
* Only perform the function if o is an Err. Returns the result of the function for chaining
*/
def static <T> Opt<T> ifErr(Opt<T> o, (Throwable)=>void fn) {
switch(o) { Err<T>: fn.apply(o.exception) }
o
}
// OPERATOR OVERLOADING ///////////////////////////////////////////////////
def static <T> T ?:(Opt<T> option, T fallback) {
if(option.defined) option.value
else fallback
}
def static <T> Opt<T> ?:(Opt<T> option, Opt<T> fallback) {
if(option.defined) option
else fallback
}
def static <T> T ?:(Opt<T> option, (Void)=>T fallback) {
if(option.defined) option.value
else fallback.apply(null)
}
// OPTION CREATION ////////////////////////////////////////////////////////
/**
* Create an option from an object. It detect if it is a None or a Some.
* <pre>api.getUser(userId).option // if getUser returns null, it will be None, otherwise Some<User></pre>
*/
def static <T> Opt<T> option(T value) {
if(value.defined) some(value)
else if(value instanceof Err<?>) err(value.exception)
else none
}
def static <T> Some<T> some(T value) {
new Some<T>(value)
}
def static <T> None<T> none() {
new None<T>
}
def static <T> Err<T> err(Throwable t) {
new Err<T>(t)
}
def static <T> Err<T> err() {
new Err<T>()
}
// ATTEMPTS ///////////////////////////////////////////////////////////////
/**
* wrap a call as an option (exception or null generates none)<p>
* example: val userOption = attempt [ api.getUser(userId) ] // if API throws exception, return None
*/
def static <T> Opt<T> attempt(=>T function) {
try function.apply.option
catch(Exception e) err(e)
}
/**
* wrap a call as an option (exception or null generates none)<p>
* example: val userOption = attempt [ api.getUser(userId) ] // if API throws exception, return None
*/
def static <P, T> Opt<T> attempt(P param, (P)=>T function) {
try function.apply(param).option
catch(Exception e) err(e)
}
/**
* Same as => but with optional execution and option result<p>
* example: normally you do: user => [ name = 'john' ]<p>
* but what if user is of type Option<User><p>
* then you can do: user.attempt [ name = 'john' ]<br>
* the assignment will only complete if there was a user
*/
def static <T, O> Opt<O> attempt(Opt<O> o, (O)=>T fn) {
if(o.defined) fn.apply(o.value)
o
}
/**
* Same as => but with optional execution and option result
* example: normally you do: user => [ name = 'john' ]
* but what if user is of type Option<User>
* then you can do: user.attempt [ name = 'john' ]
* the assignment will only complete if there was a user
* <p>
* This version accept functions that have no result
*/
def static <T, O> Opt<O> attempt(Opt<O> o, (O)=>void fn) {
if(o.defined) fn.apply(o.value)
o
}
// MAPPING ////////////////////////////////////////////////////////////////
/**
* Transform an option into a new option using a function.
* The function allows you to transform the value of the passed option,
* saving you the need to unwrap it yourself
*/
def static <T, I> Opt<T> map(Opt<I> o, (I)=>T fn) {
if(o.defined) fn.apply(o.value).option else none
}
// FLATTEN ////////////////////////////////////////////////////////////////
/**
* Flatten a double wrapped optional back to a single optional
*/
def static <T> Opt<T> flatten(Opt<Opt<T>> option) {
switch option {
Some<Opt<T>>: option.value
None<Opt<T>>: none
Err<Opt<T>>: err(option.exception)
}
}
// OPTIONAL FALLBACK EXTENSIONS ///////////////////////////////////////////
/**
* Provide a fallback value if o is undefined
* <pre>val user = foundUser.or(defaultUser)</pre>
*/
def static <T> T or(T o, T fallback) {
if(o.defined) o else fallback
}
/**
* provide a fallback value if o is undefined
* <pre>val user = api.getUser(12).or(defaultUser) // getUser returns an Option<User></pre>
*/
def static <T> T or(Opt<T> o, T fallback) {
if(o.defined) o.value else fallback
}
// run a fallback function if o is undefined
// example: val user = foundUser.or [ api.getDefaultUser() ]
def static <T> T or(T o, (Object)=>T fallbackFn) {
if(o.defined) o else fallbackFn.apply(null)
}
// run a fallback function if o is undefined
// example: val user = api.getUser(12).or [ api.getDefaultUser() ] // getUser returns an Option<User>
def static <T> T or(Opt<T> o, (Object)=>T fallbackFn) {
if(o.defined) o.value else fallbackFn.apply(null)
}
def static <T> T orNull(T o) {
if(o.defined) o else null
}
def static <T> T orNull(Opt<T> o) {
if(o.defined) o.value else null
}
def static <T> T orThrow(Opt<T> o) {
switch(o) {
Err<T>: throw o.exception
None<T>: throw new NoneException
Some<T>: o.value
}
}
// try to unwrap an option, and if there is nothing, throws an exception
// example: val user = api.getUser(12).orThrow('could not find user') // getUser returns an option
def static <T> T orThrow(Opt<T> o, String s) {
if(o.defined) o.value else throw new NoneException(s)
}
// try to unwrap an option, and if there is nothing, calls the exceptionFn to get a exception to throw
// example: val user = api.getUser(12).orThrow [ new UserNotFoundException ] // getUser returns an option
def static <T> T orThrow(Opt<T> o, (Object)=>Throwable exceptionFn) {
if(o.defined) o.value else throw exceptionFn.apply(null)
}
}