What is the `arrow-fx-stm` dependency?
# arrow
l
What is the
arrow-fx-stm
dependency?
s
It should be
io.arrow-kt:arrow-fx-stm:1.1.3
?
l
Yes
But what is it? What does it do?
Do I need it if I use Kotlinx coroutines?
s
l
Thanks!
s
It's a pattern of working with mutable state in safe ways in face of concurrency and parallelism.
I see the documentation is hidden quite deeply on the website :s Here is a tutorial style doc, https://arrow-kt.io/docs/apidocs/arrow-fx-stm/arrow.fx.stm/-s-t-m/index.html
That should give you a decent intro into the module ☺️
l
I just need something to use retrying mechanism in coroutines, so I guess
arrow-fx-coroutines
should be enough for my use case.
s
Yes,
Schedule
should be perfect for that use-case.
l
Amazing, thanks for your help as always!
s
We've been experimenting with nicer implementations for
Schedule
, so if you have any feedback let me know 🙏
My pleasure as always ☺️
l
Maybe you can point me to the right functions though: I have a backend call which returns
Either<CallError, T>
and I want to retry it as long as the result is a
Left<HttpError>
with code
403
(
HttpError
is a child of
CallError
). I want to retry with exponential backoff for 60 seconds and then give up with some error.
s
I think something like this will work,
Copy code
@OptIn(ExperimentalTime::class)
fun <A> schedule(): Schedule<Either<HttpError, A>, Either<HttpError, A>> =
  Schedule.exponential<Either<HttpError, A>>(1.seconds)
    .check { either: Either<HttpError, A>, duration ->
      (either as? Either.Left<HttpError>)?.value?.code == 403 && duration < 60.seconds
    }.zipRight(Schedule.identity())
l
where do I put the API call to be retried?
s
schedule<A>().repeat { call() }
l
Ah ok
I thought
repeat()
is only for calls that succeeded, I was wondering if there is analogous
retry()
for calls that fail (as the documentation hints that this is a different behavior).
s
This basically "repeats" the
call
according to the schedule. So any other
HttpError
would be considered "succes" and won't be repeated. It will also only repeat exponentially with a limit of
60.seconds
, but you could also add
and(Schedule.recurs(n))
if you'd wanted to limit to a certain amount of retries
l
Awesome
s
Right, that's why we're re-imagining it completely and might put it in Core but it's a tricky pattern to design well. The current implementation is not really nice on the insight, and the API is also a bit clunky as you can see. This is what we currently have working
Copy code
repeat(1.seconds, Policy.Exponential())
  .retry<Either<HttpError, A>> { 
      val res: Either<HttpError, A> = call()
     ensure(res.leftOrNull()?.code != 403) { ??? }
   }
l
Looks interesting.
s
Better integration with the DSL is vital for Arrow since then it would automatically work with context receivers, and Either, etc
l
In parallel, I think even just providing some more examples of retrying logic with the current API (like you provided now for my use case) would already help a great deal. Maybe, after I play around with it a little more, I will open a PR with some more example for the website.
s
That would be really great @Lukasz Kalnik 🙏 Having examples of common use-cases would also be really great if we do a newly designed type.
l
Definitely!
I only had to slightly modify your example because you cannot cast to
Left<HttpError>
because of type erasure:
Copy code
.check { either: Either<CallError, AirConnectorSystem>, duration ->
                either is Left<*> &&
                        either.value is HttpError &&
                        (either.value as HttpError).code == 403 &&
                        duration < 60.seconds
            }
Because we actually have an error hierarchy, were
HttpError
is a child of
CallError
(I think I didn't mention that in my initial question actually).
s
Right, wish there was a simpler way of writing this.
l
I think it's simple enough, the only confusing part here was the
zipRight()
for me
Maybe just having a
retry()
function which calls
zipRight(Schedule.identity()
.repeat() would be a way to make it easier? I'm not sure if it's semantically correct though, but something along those lines. I think many times when retrying the users will not be actually interested in the output of the
Schedule
, but in the last (successful) output of the actual function being repeated.
s
Right, that is something we also want to get rid of in the new design. In 99,99% of the cases I've used it people use
zipRight(Schedule.identity())
so it makes more sense to offer a separate
retryOutput
functions for the very few times you want it.
l
That should be probably even easy to integrate with the existing API for the time being?
Maybe I will open a PR for this as well.
s
Yes, we can definitely do that if we can improve the current API. That'd be really great
l
I will give it a try.