Gopal S Akshintala
02/28/2020, 11:11 AMfx.async{}
block below:
fun <F> Async<F>.something(): Kind<F, Either<String, String>> = fx.async {
val left = "1-left".left().just()
// I wish to short-circuit here if either above is left
"2-right".right()
}
This is what I could come up with:
fun <F> Async<F>.something2(): Kind<F, Either<String, String>> = fx.async {
Either.fx {
!"1-left".left()
"2-right".right().bind()
}
}
Is there a better way than to use nested blocks?Jannis
02/28/2020, 1:27 PMEitherT<L, F, A>
is a wrapper around Kind<F, Either<L, A>>
which has a monad instance that flatMaps through the nested either.
I do not recommend using it directly in code, what should be done instead is: fun <F, E, A> something(AS: Async<F>, ME: MonadError<F, E>): Kind<F, A>
and then later use EitherT.async
and EitherT.monadError
to fill in instances.
I know I said "I do not recommend using it directly in code" just above, but currently this won't be possible because `EitherT`'s MonadError
instance is wrong! (Just noticed that). So if you use fun something(AS: Async<F>): EitherT<L, F, A> = EitherT.async(AS).fx {}
(or similar, don't quite remember the exact syntax) you should get a binding where you can do EitherT.left(error).bind()
and it will short circuit.
I think with 0.11 you can also do the same thing with bifunctor IO
. So you can call something(IO.async(), IO.monadError())
. But if you don't use IO
mtl is your best bet. cc @simon.vergauwen is my assumption that MonadError
will use E
and not Throwable
correct? o.O
This is quite the ugly pattern right now, but just imagine this pattern with actual typeclass resolution from arrow meta. That is when this pattern becomes really powerful and easy to use.
For right now and maybe further reading: There is no good way other than what you are doing. Monads do not compose nicely and there are a lot of different attempts to solve these problems:
• Bifunctor IO fuses two commonly used monads to avoid the need for composing monads (0.11 IO
type in arrow)
• mtl composes monads through transformers (lots of little drawbacks everywhere, but better than manually composing them. This pattern is ugly in kotlin without arrow-meta, which is not yet done :/)
• algebraic effect systems (just look it up if you are interested. The basic thing is possible using Free
in arrow, but not without quite a lot overhead in both boilerplate and performance. With meta it might be possible to get a fast effect system...). This pattern is imo the best solution there is, but that is a long time away in kotlin/arrow, if at all possible.
Sorry for the long answer, this also touches a lot of complex topics, so if you have questions don't hesitate to ask!Jannis
02/28/2020, 1:34 PMEitherT
has it's generic argument order changed in the latest snapshot. I used the new order EitherT<L, F, A>
here. If you use an older version 0.10.4
or prior just substitute that with EitherT<F, L, A>
simon.vergauwen
02/29/2020, 11:28 AMcc @simon.vergauwen is my assumption thatYou can define lawful instances for both.will useMonadError
and notE
correct? o.OThrowable
Jannis
02/29/2020, 12:41 PME
as the default instance and using MonadThrow
for Throwable
sounds most sensible to me. EitherT
also needs to make that choice for MonadError
, MonadThrow
and Bracket
and I am not quite sure whats best.simon.vergauwen
02/29/2020, 12:42 PMYes aboslutely! Didn’t think aboutforMonadThrow
sounds most sensibleThrowable
MonadThrow
simon.vergauwen
02/29/2020, 12:43 PMEitherT
?Jannis
02/29/2020, 12:50 PMMonadThrow
of F
. Bracket
is not so easy of a choice thoughsimon.vergauwen
02/29/2020, 12:50 PMBracket
what we can do in the impl.simon.vergauwen
02/29/2020, 12:51 PMBracketThrow<F, E>
simon.vergauwen
02/29/2020, 12:51 PMExitCaseThrow<E>
simon.vergauwen
02/29/2020, 12:53 PMJannis
02/29/2020, 1:02 PMIOBracket<E> : Bracket<IOPartialOf<E>, E>
then there is no way for a user to define bracket for Throwable
(without going to internals or local functions for IO
). These are the hard parts of bifunctor IO
and in general EitherT
error handling. If we choose the Throwable
instance it might be weird for those that extensively use E
. If we choose E
it will be very hard to handle unexpected errors thrown from impure code of which there is plenty out there in the jvm ecosystem. Exposing both seems best, but not through additional typeclasses, because that is an IO
specific problem. EitherT
doesn't have this problem because you can always go a level lower and use IO
if you need it, with bifunctor IO
and the wrong instance that choice is taken away. The problem is that with typeclass resolution there is likely a need to constrain it to one instance only, so we might need to go for additional typeclasses just to have our IO
work in a polymorphic setting. We also need to expose multiple versions of bracket: One for E
and one for Throwable
. This is a bit messy 😅simon.vergauwen
02/29/2020, 1:03 PMJannis
02/29/2020, 1:10 PM