Monad transformers

Either and Option are simple to use, but what happens if we combine both?

object UserService {

fun findAge(user: String): Either<String, Option<Int>> {
//Magic
}
}

UserService.findAge returns Either<String, Option<Int>>; Left<String> for errors accessing the database or any other infrastructure, Right<None> for no value found on the database, and Right<Some<Int>> for a value found:

import arrow.core.*
import arrow.syntax.function.pipe

fun main(args: Array<String>) {
val anakinAge: Either<String, Option<Int>> = UserService.findAge("Anakin")

anakinAge.fold(::identity, { op ->
op.fold({ "Not found" }, Int::toString)
}) pipe ::println
}

To print an age, we need two nested folds, nothing too complicated. Problems arrive when we need to do operations accessing multiple values:

import arrow.core.*
import arrow.syntax.function.pipe
import kotlin.math.absoluteValue

fun main(args: Array<String>) {
val anakinAge: Either<String, Option<Int>> = UserService.findAge("Anakin")
val padmeAge: Either<String, Option<Int>> = UserService.findAge("Padme")

val difference: Either<String, Option<Either<String, Option<Int>>>> = anakinAge.map { aOp ->
aOp.map { a ->
padmeAge.map { pOp ->
pOp.map { p ->
(a - p).absoluteValue
}
}
}
}

difference.fold(::identity, { op1 ->
op1.fold({ "Not Found" }, { either ->
either.fold(::identity, { op2 ->
op2.fold({ "Not Found" }, Int::toString) })
})
}) pipe ::println
}

Monads don't compose, making these operations grow in complexity, very quickly. But, we can always count on comprehensions, can't we? Now, let's look at the following codes:

import arrow.core.*
import arrow.syntax.function.pipe
import arrow.typeclasses.binding
import kotlin.math.absoluteValue


fun main(args: Array<String>) {
val anakinAge: Either<String, Option<Int>> = UserService.findAge("Anakin")
val padmeAge: Either<String, Option<Int>> = UserService.findAge("Padme")

val difference: Either<String, Option<Option<Int>>> = Either.monad<String>().binding {
val aOp: Option<Int> = anakinAge.bind()
val pOp: Option<Int> = padmeAge.bind()
aOp.map { a ->
pOp.map { p ->
(a - p).absoluteValue
}
}
}.ev()

difference.fold(::identity, { op1 ->
op1.fold({ "Not found" }, { op2 ->
op2.fold({ "Not found" }, Int::toString) }) }) pipe ::println
}

 This is better, the returning type is not that long, and fold is more manageable. Let's take a look at the nested comprehensions in the following code snippet:

fun main(args: Array<String>) {
val anakinAge: Either<String, Option<Int>> = UserService.findAge("Anakin")
val padmeAge: Either<String, Option<Int>> = UserService.findAge("Padme")

val difference: Either<String, Option<Int>> = Either.monad<String>().binding {
val aOp: Option<Int> = anakinAge.bind()
val pOp: Option<Int> = padmeAge.bind()
Option.monad().binding {
val a: Int = aOp.bind()
val p: Int = pOp.bind()
(a - p).absoluteValue
}.ev()
}.ev()

difference.fold(::identity, { op ->
op.fold({ "Not found" }, Int::toString)
}) pipe ::println
}

Now, we have the same type of both values and result. But we still have another option, monad transformers.

A monad transformer is a combination of two monads that can be executed as one. For our example, we will use OptionT, (shorthand for Option Transformer) as Option is the monad type that is nested inside Either:

import arrow.core.*
import arrow.data.OptionT
import arrow.data.monad
import arrow.data.value
import arrow.syntax.function.pipe
import arrow.typeclasses.binding
import kotlin.math.absoluteValue


fun main(args: Array<String>) {
val anakinAge: Either<String, Option<Int>> = UserService.findAge("Anakin")
val padmeAge: Either<String, Option<Int>> = UserService.findAge("Padme")

val difference: Either<String, Option<Int>> = OptionT.monad<EitherKindPartial<String>>().binding {
val a: Int = OptionT(anakinAge).bind()
val p: Int = OptionT(padmeAge).bind()
(a - p).absoluteValue
}.value().ev()

difference.fold(::identity, { op ->
op.fold({ "Not found" }, Int::toString)
}) pipe ::println
}

We use OptionT.monad<EitherKindPartial<String>>().binding. The EitherKindPartial<String> monad means that the wrapper type is an Either<String, Option<T>>.

Inside the binding block, we use OptionT on values of type Either<String, Option<T>> (technically on values of type HK<HK<EitherHK, String>, Option<T>>) to call bind(): T, in our case T, is Int.

Previously we used just the ev() method, but now we need to use the value() method to extract the OptionT internal value.

In our next section, we'll learn about the Try type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.142.166