13
13
* See the License for the specific language governing permissions and
14
14
* limitations under the License.
15
15
*/
16
+ @file:Suppress(" DEPRECATION" )
17
+
16
18
package com.fernandocejas.sample.core.functional
17
19
20
+ import com.fernandocejas.sample.core.functional.Either.Left
21
+ import com.fernandocejas.sample.core.functional.Either.Right
22
+
18
23
/* *
19
24
* Represents a value of one of two possible types (a disjoint union).
20
25
* Instances of [Either] are either an instance of [Left] or [Right].
@@ -26,10 +31,14 @@ package com.fernandocejas.sample.core.functional
26
31
*/
27
32
sealed class Either <out L , out R > {
28
33
/* * * Represents the left side of [Either] class which by convention is a "Failure". */
29
- data class Left <out L >(val a : L ) : Either<L, Nothing>()
34
+ data class Left <out L >
35
+ @Deprecated(" .toLeft()" , ReplaceWith (" a.toLeft()" ))
36
+ constructor (val a: L ) : Either <L , Nothing >()
30
37
31
38
/* * * Represents the right side of [Either] class which by convention is a "Success". */
32
- data class Right <out R >(val b : R ) : Either<Nothing, R>()
39
+ data class Right <out R >
40
+ @Deprecated(" .toRight()" , ReplaceWith (" b.toRight()" ))
41
+ constructor (val b: R ) : Either <Nothing , R >()
33
42
34
43
/* *
35
44
* Returns true if this is a Right, false otherwise.
@@ -44,30 +53,63 @@ sealed class Either<out L, out R> {
44
53
val isLeft get() = this is Left <L >
45
54
46
55
/* *
47
- * Creates a Left type .
56
+ * Applies fnL if this is a Left or fnR if this is a Right .
48
57
* @see Left
49
- */
50
- fun <L > left (a : L ) = Either .Left (a)
51
-
52
-
53
- /* *
54
- * Creates a Left type.
55
58
* @see Right
56
59
*/
57
- fun <R > right (b : R ) = Either .Right (b)
60
+ fun <T > fold (fnL : (L ) -> T , fnR : (R ) -> T ): T =
61
+ when (this ) {
62
+ is Left -> fnL(a)
63
+ is Right -> fnR(b)
64
+ }
58
65
59
66
/* *
60
67
* Applies fnL if this is a Left or fnR if this is a Right.
68
+ *
69
+ * Kotlin Coroutines Support.
61
70
* @see Left
62
71
* @see Right
63
72
*/
64
- fun fold (fnL : (L ) -> Any , fnR : (R ) -> Any ): Any =
73
+ suspend fun < T > coFold (fnL : suspend (L ) -> T , fnR : suspend (R ) -> T ): T =
65
74
when (this ) {
66
75
is Left -> fnL(a)
67
76
is Right -> fnR(b)
68
77
}
78
+
79
+ companion object {
80
+ /* *
81
+ * Transforms a try/catch in an Either<Exception, Right>
82
+ * See [mapException]
83
+ * **/
84
+ @Suppress(" TooGenericExceptionCaught" )
85
+ suspend fun <Right > catch (
86
+ operation : suspend () -> Right
87
+ ): Either <Exception , Right > =
88
+ try {
89
+ operation().toRight()
90
+ } catch (e: Exception ) {
91
+ e.toLeft()
92
+ }
93
+ }
69
94
}
70
95
96
+ /* * If Left is of type Exception, allows to map it to another type.
97
+ * Rethrow the exception if [operation] returns null **/
98
+ fun <Left : Any , Right > Either <Exception , Right >.mapException (
99
+ operation : (Exception ) -> Left ?
100
+ ): Either <Left , Right > = when (this ) {
101
+ is Either .Left -> operation(a)?.toLeft() ? : throw a
102
+ is Either .Right -> this
103
+ }
104
+
105
+ /* * Represents the right side of [Either] class which by convention is a "Success". **/
106
+ fun <T > T.toRight (): Right <T > =
107
+ Right (this )
108
+
109
+ /* * Represents the left side of [Either] class which by convention is a "Failure". */
110
+ fun <T > T.toLeft (): Left <T > =
111
+ Left (this )
112
+
71
113
/* *
72
114
* Composes 2 functions
73
115
* See <a href="https://proandroiddev.com/kotlins-nothing-type-946de7d464fb">Credits to Alex Hart.</a>
@@ -82,37 +124,64 @@ fun <A, B, C> ((A) -> B).c(f: (B) -> C): (A) -> C = {
82
124
*/
83
125
fun <T , L , R > Either <L , R >.flatMap (fn : (R ) -> Either <L , T >): Either <L , T > =
84
126
when (this ) {
85
- is Either . Left -> Either . Left (a)
86
- is Either . Right -> fn(b)
127
+ is Left -> Left (a)
128
+ is Right -> fn(b)
87
129
}
88
130
89
131
/* *
90
- * Right-biased map () FP convention which means that Right is assumed to be the default case
132
+ * Right-biased flatMap () FP convention which means that Right is assumed to be the default case
91
133
* to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged.
134
+ * It works with Kotlin Coroutines (suspension functions).
92
135
*/
93
- fun <T , L , R > Either <L , R >.map (fn : (R ) -> (T )): Either <L , T > = this .flatMap(fn.c(::right))
94
-
95
- /* * Returns the value from this `Right` or the given argument if this is a `Left`.
96
- * Right(12).getOrElse(17) RETURNS 12 and Left(12).getOrElse(17) RETURNS 17
97
- */
98
- fun <L , R > Either <L , R >.getOrElse (value : R ): R =
136
+ suspend fun <T , L , R > Either <L , R >.coFlatMap (fn : suspend (R ) -> Either <L , T >): Either <L , T > =
99
137
when (this ) {
100
- is Either . Left -> value
101
- is Either . Right -> b
138
+ is Left -> Left (a)
139
+ is Right -> fn(b)
102
140
}
103
141
104
142
/* *
105
143
* Left-biased onFailure() FP convention dictates that when this class is Left, it'll perform
106
144
* the onFailure functionality passed as a parameter, but, overall will still return an either
107
145
* object so you chain calls.
108
146
*/
109
- fun <L , R > Either <L , R >.onFailure (fn : (failure: L ) -> Unit ): Either <L , R > =
110
- this .apply { if (this is Either . Left ) fn(a) }
147
+ infix fun <L , R > Either <L , R >.onFailure (fn : (failure: L ) -> Unit ): Either <L , R > =
148
+ this .apply { if (this is Left ) fn(a) }
111
149
112
150
/* *
113
151
* Right-biased onSuccess() FP convention dictates that when this class is Right, it'll perform
114
152
* the onSuccess functionality passed as a parameter, but, overall will still return an either
115
153
* object so you chain calls.
116
154
*/
117
- fun <L , R > Either <L , R >.onSuccess (fn : (success: R ) -> Unit ): Either <L , R > =
118
- this .apply { if (this is Either .Right ) fn(b) }
155
+ infix fun <L , R > Either <L , R >.onSuccess (fn : (success: R ) -> Unit ): Either <L , R > =
156
+ this .apply { if (this is Right ) fn(b) }
157
+
158
+ /* *
159
+ * Right-biased map() FP convention which means that Right is assumed to be the default case
160
+ * to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged.
161
+ */
162
+ fun <L , R , T > Either <L , R >.map (fn : (R ) -> (T )): Either <L , T > =
163
+ when (this ) {
164
+ is Left -> Left (a)
165
+ is Right -> Right (fn(b))
166
+ }
167
+
168
+ /* *
169
+ * Right-biased map() FP convention which means that Right is assumed to be the default case
170
+ * to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged.
171
+ * It works with Kotlin Coroutines (suspension functions).
172
+ */
173
+ suspend fun <L , R , T > Either <L , R >.coMap (fn : suspend (R ) -> (T )): Either <L , T > =
174
+ when (this ) {
175
+ is Left -> Left (a)
176
+ is Right -> Right (fn(b))
177
+ }
178
+
179
+ /* *
180
+ * Returns the value from this `Right` or the given argument if this is a `Left`.
181
+ * Right(12).getOrElse(17) RETURNS 12 and Left(12).getOrElse(17) RETURNS 17
182
+ */
183
+ fun <L , R > Either <L , R >.getOrElse (value : R ): R =
184
+ when (this ) {
185
+ is Left -> value
186
+ is Right -> b
187
+ }
0 commit comments