1313 * See the License for the specific language governing permissions and
1414 * limitations under the License.
1515 */
16+ @file:Suppress(" DEPRECATION" )
17+
1618package com.fernandocejas.sample.core.functional
1719
20+ import com.fernandocejas.sample.core.functional.Either.Left
21+ import com.fernandocejas.sample.core.functional.Either.Right
22+
1823/* *
1924 * Represents a value of one of two possible types (a disjoint union).
2025 * Instances of [Either] are either an instance of [Left] or [Right].
@@ -26,10 +31,14 @@ package com.fernandocejas.sample.core.functional
2631 */
2732sealed class Either <out L , out R > {
2833 /* * * 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 >()
3037
3138 /* * * 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 >()
3342
3443 /* *
3544 * Returns true if this is a Right, false otherwise.
@@ -44,30 +53,63 @@ sealed class Either<out L, out R> {
4453 val isLeft get() = this is Left <L >
4554
4655 /* *
47- * Creates a Left type .
56+ * Applies fnL if this is a Left or fnR if this is a Right .
4857 * @see Left
49- */
50- fun <L > left (a : L ) = Either .Left (a)
51-
52-
53- /* *
54- * Creates a Left type.
5558 * @see Right
5659 */
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+ }
5865
5966 /* *
6067 * Applies fnL if this is a Left or fnR if this is a Right.
68+ *
69+ * Kotlin Coroutines Support.
6170 * @see Left
6271 * @see Right
6372 */
64- fun fold (fnL : (L ) -> Any , fnR : (R ) -> Any ): Any =
73+ suspend fun < T > coFold (fnL : suspend (L ) -> T , fnR : suspend (R ) -> T ): T =
6574 when (this ) {
6675 is Left -> fnL(a)
6776 is Right -> fnR(b)
6877 }
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+ }
6994}
7095
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+
71113/* *
72114 * Composes 2 functions
73115 * 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 = {
82124 */
83125fun <T , L , R > Either <L , R >.flatMap (fn : (R ) -> Either <L , T >): Either <L , T > =
84126 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)
87129 }
88130
89131/* *
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
91133 * to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged.
134+ * It works with Kotlin Coroutines (suspension functions).
92135 */
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 > =
99137 when (this ) {
100- is Either . Left -> value
101- is Either . Right -> b
138+ is Left -> Left (a)
139+ is Right -> fn(b)
102140 }
103141
104142/* *
105143 * Left-biased onFailure() FP convention dictates that when this class is Left, it'll perform
106144 * the onFailure functionality passed as a parameter, but, overall will still return an either
107145 * object so you chain calls.
108146 */
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) }
111149
112150/* *
113151 * Right-biased onSuccess() FP convention dictates that when this class is Right, it'll perform
114152 * the onSuccess functionality passed as a parameter, but, overall will still return an either
115153 * object so you chain calls.
116154 */
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