I’m struggling to understand how to replace Try wi...
# arrow
c
I’m struggling to understand how to replace Try with the suspending constructors of Either in the context of monad transformers. Can someone point out to me how to replace Try with Either.catch in my code?
Copy code
import arrow.core.Either
import arrow.core.ForEither
import arrow.core.Option
import arrow.core.Try
import arrow.fx.ForIO
import <http://arrow.fx.IO|arrow.fx.IO>
import <http://arrow.fx.extensions.io|arrow.fx.extensions.io>.applicative.applicative
import arrow.mtl.EitherT
import arrow.mtl.OptionT


typealias IOResult = EitherT<ForIO, Throwable, OptionT<ForEither, Int>>
val doesNotWork = IOResult.fromEither(IO.applicative(), Either.catch { Option.just(3) })
val doesWork = IOResult.fromEither(IO.applicative(), Try { Option.just(3)}.toEither())
r
EitherT over throwable is pointless because Throwable is implied in IO already
Also the types in the results don't really match the transformer stack in the alias. What is the common result type you are working on in your functions? IO and what else?
c
@raulraja My idea was to create a safe version of an impure function that hits db and returns a nullable type and might throw an exception. Here is my attempt to describe what I’m trying to accomplish:
Copy code
import arrow.core.Either
import arrow.core.Option
import arrow.core.Tuple2
import arrow.fx.ForIO
import <http://arrow.fx.IO|arrow.fx.IO>
import <http://arrow.fx.extensions.io|arrow.fx.extensions.io>.monad.monad
import arrow.mtl.EitherT
import arrow.mtl.EitherTPartialOf
import arrow.mtl.OptionT
import arrow.mtl.extensions.eithert.monad.monad
import arrow.mtl.extensions.fx

typealias OptionInEitherInIO = OptionT<EitherTPartialOf<ForIO, Throwable>, String>

//some library function that is outside of my control, this function hits the db and throws exceptions
fun findInDB(id: String): String? = TODO()

//this is the type i think is appropriate for modelling -> Throwable should be replaced by a failure datatype in the end
fun makeSafe(f: (String) -> String?): (String) -> IO<Either<Throwable, Option<String>>> = TODO()

//the monad transformers should allow for monad comprehension
fun makeSafe2(f: (String) -> String?): (String) -> OptionInEitherInIO = TODO()

//this is what i try to accomplish
val safeFind = makeSafe2(::findInDB)
val monad = EitherT.monad<ForIO, Throwable>(IO.monad())
val result = OptionT.fx(monad) {
    val s1 = safeFind("foo").bind()
    val s2 = safeFind(s1).bind()

    Tuple2(s1, s2)
}
r
I think in this case you achieve the same guarantees with just suspend and IO.
Copy code
suspend fun safeFindInDB(id: String): String? = findInDb(id)
val result: IO<Either<Throwable, String?> = IO.effect { safeBindInDB("someId") }.attempt()
// Use EitherT over values of result
val sink = Either<Throwable, String?> = result.unsafeRunSync() //don't do this in prod
c
Thanks for the pointer. But I still don’t know how to achieve a monad comprehension that combines the behavior of IO, Either and Option. Sorry for the noob questions …
j
If you want to do mtl with arrow you have to do two things: Define lots of extension functions that partially apply all the
Applicative
Monad
instances required everywhere for your concrete monad-stack. And keep a good typealias. If you want lookups that can return None, can also fail with an error E and need side-effects you are looking at something like
OptionT<EitherTPartial<ForIO, E>, A>
which when unwrapped is equal to
IO<Either<E, Option<A>>>
. Few good rules of thumb: Always have IO at the bottom of the stack and always mentally unwrap it to it's concrete rep to understand what is happening.
EitherT<M, E, A> = Kind<M, Either<E, A>>
,
OptionT<M, A> = Kind<M, Option<A>>
and other mtl instances are similar. Also something you will need is good support for lifting values, define a common function lift which goes from
Kind<M, A>
to your monadstack (if it contains M that is^^) There is also room for a pr to add something like a
MonadTrans
class (i did start it a while back but got stuck on a few annoying parts about generic parameter order)
I have done a bit of mtl with arrow and I'd say it is definitly useable, but not the nicest thing in the world atm and in the long-run you are probably better of with
IO<E, A>
instead. If you have long chains of code where this optional behaviour you have with
OptionT
persists it is definitly better then nullable types or using
IO<E, Option<A>>
(you can also use
OptionT<IO<E, A>>
there) but it will always come at a certain cost to do such a thing. I personally would only go for mtl if something like
flatMap { it.flatMap { ... } }
happens over and over again or if I need to constantly wrap and unwrap values in fx blocks.
Mtl however becomes easier to use with arrow-meta plugins: With typeclass instance resolution the need for the partially applied extension functions goes away and the mess with
Kind<*, *>
is also resolved.
IO<E, A>
with still be a lot faster and easier to use than
EitherT<ForIO, E, A>
, but the gap won't be that far ^^ With proper newtypes that actually fully unwrap at runtime, if that is possible, you can get the performance issues under control and then mtl becomes actually viable. Although mtl on it's own still has annoying parts and I like algebraic effects much more, but thats a different story.
c
@Jannis Thanks for your detailed explanations and tips! As I understand it IO<E, A> does not exist yet, does it? Do you have any pointer on how to use the arrow-meta plugins for mtl?
j
Both don't exist yet,
IO<E, A>
is in the works and will come out in with arrow 0.11 in the next 1-3 months I think (don't quote me on that 🙈). Arrow meta is also under active development but that's a bit further out
Mtl however becomes easier to use with arrow-meta plugins:
This would have been better phrased as "Mtl will however become easier to use with arrow-meta plugins:" ...
r
To confirm what @Jannis said BIO will be out soon, @simon.vergauwen is working on it and the type classes, automatic injections and other goodies that make FP ergonomic around April or before depending on the 1.4 compiler roadmap.
❤️ 5