Hi guys, I would be helpful for any hints on how ...
# arrow-meta
d
Hi guys, I would be helpful for any hints on how to implement retry with
IO<Either<DomainError, Value>>
. I would like to retry not only on
Exception
reported within
IO
context but also on logical
DomainError
reported within contained
Either
. The only thing connected to retries I found is https://github.com/luisdeltoro/arrow-effects-retry
s
Hey @dnowak, Awesome that you’re looking into this! @Jannis build a polymorphic
Schedule
which works for any
F
. It allows you to retry based on the
Exception
or the
value
produced by the
F
. So you can also use
Schedule
to build complex retries based of the
E
in your
Either
. https://next.arrow-kt.io/docs/apidocs/arrow-fx/arrow.fx/-schedule/
https://github.com/arrow-kt/arrow/tree/release/0.11.0 For the next minor release, we are also adding
E
to
IO
so you can also track custom domain errors within
IO
without having to nest
Either
or rely on
EitherT
for specialised syntax.
Schedule
is available on
0.10.5-SNAPSHOT
, which will be released in the following weeks. We’re finishing the release, and adding a deprecation cycle for breaking signatures when adding
E
to
IO
.
Small note: https://github.com/luisdeltoro/arrow-effects-retry/blob/master/src/main/kotlin/io/luisdeltoro/arrow/effects/retry/timer.kt Arrow Fx already exposes such a
Timer
typeclass which has a proper cancelable implmentation. The implementation here based on KotlinX is unsafe and uncancelable 😉
j
@dnowak If you want to have
Schedule
work with
IO<Either<DomainError, Value>>
prior to
0.11
Bifunctor IO you can always do
EitherT(ioWithEither).retry(EitherT.monadError(), Timer(EitherT.concurrent(IO.concurrent()))), schedule)
and if you want to get your
IO<Either<DomainError, Value>>
back just call
value()
on the resulting
IO
. (That needs a dependency on
arrow-fx-mtl
). Also take a look at simons example below, that works without
EitherT
s
@Jannis
Schedule
also supports retrying based on the
value
, right? So you can already retry based on encountering certain
Left
values, no?
j
Repeating is based on the value, retry will default to the error iirc
Will take a quick look at the implementation first...
retry
when given a
MonadError + Timer
instead of
Concurrent
is polymorphic in
E
and will take the errors from the
MonadError
instance. But `IO`'s current
MonadError
instance is fixed to
Throwable
right? So you would need either a custom
MonadError
instance, or just use
EitherT
That makes me want to remove the constructors using
Concurrent
...
s
You can use
repeat
to
retry
values. You can retry values by simply composing
flatMap
, right?
Copy code
fun <E, A> retryDomainOnLeftSchedule(): Schedule<ForIO, Either<E, A>, Either<E, A>> =
    Schedule.withMonad(IO.monad()) {
        doWhile<Either<E, A>> { it is Either.Left }
    }

suspend fun main(): Unit =
    IO.effect { println("Hello World!") }
        .map { it.left() }
        .repeat(IO.concurrent(), retryDomainOnLeftSchedule())
        .unit()
        .suspended()
This retries on
Left
until
Right
is found,
doWhile<Either<E, A>> { it is Either.Left }
. So this will print “Hello World” forever
💯 1
doWhile
+
repeat
is a perfect retry mechanism for effectful values.
j
That's clever ^^
It won't catch exceptions within
IO
but neither would
EitherT
so that's fine I guess
s
It does catch exceptions within
IO
? And so does
EitherT
if stacked with
IO
Why wouldn’t it?
You should still take care of exceptions separate from retrying your domain if that is what you meant.
j
You should still take care of exceptions separate from retrying your domain if that is what you meant.
Yes exactly.
retry
with
IO.concurrent
will only be triggered if there is an error inside
IO
for example.
retry
with
EitherT.monadError
will only trigger on
Kind<F, Left...>
etc
So with
repeat
you need to handle errors manually, which given that they are already in
IO<Either<L, R>>
form is probably already a thing
s
For the effect typeclass the
E
side in
MonadError
is always fixed to
Throwable
.
Effects imply
Throwable
, so all effect typeclasses have to deal with that. Hence
MonadThrow
j
Yes, thats why
retry
and
repeat
take
MonadError + Timer
instead, so that domain errors can be used. The concurrent overload is more of a convience thing. This needs better documentation when
0.11
comes around!
1
https://github.com/arrow-kt/arrow/blob/master/modules/fx/arrow-fx/src/main/kotlin/arrow/fx/Schedule.kt#L874 below has the full thing.
Concurrent<F>
is always split into
MonadError<F, Throwable>
and
Timer<F>
and the main methods that implement everything work on
MonadError
and
Timer
and not
Concurrent
s
Right I see, I missed that part of
ME + Timer
for that reason
d
Thanks a lot for all the responses.