That does worry me a little bit, the docs <https:/...
# arrow
b
That does worry me a little bit, the docs https://arrow-kt.io/docs/effects/fx/ indicate using
IO {...}
in conjunction with
effect {...}
but ^^ the example above gets to SOE pretty quickly when attempting to use the two together
p
the example above meaning your original example?
or the rewrite with tailcallM
because if it’s the later wtf lol
b
the rewrite with tailRecM
everything above tailRecM was
IO
, but the mapper called by tailRecM used effect
I'll check 0.10-SNAPSHOT to see if the problem still exists
p
…without recursion?
weird
b
let me put up a gist of the whole thing
Running that against a table with ~1400 users results in the SOE
is there something in 0.9.0 where interleaving thunks and coroutines prevents TCO?
p
ye, probably
as @simon.vergauwen mentioned we fixed a couple somewhere in Sprint
the fx in line 53 is superflous btw
you're immediately calling something and not doing anything with the result
b
hah, you're right, it is
TBH I'm not convinced the code in the gist is any better than wrapping up a simple non-IO while loop in an
IO
block since the likely error handling is either the entire retrieval succeeds or fails
at least for my typical use cases
p
I did’t check the code much, but it seems like a reasonable default.
you can skip IO and use suspend if you don’t need changing threads or awaiting other IOs
b
I am trying to bridge ktor and simple JDBC access using IO the effect to run whatever program services each route in ktor
the idea being that I can add the equivalent of global exception handlers/filters to ktor and return appropriate HTTP responses based on the outcome of
IO
So I think I need
IO
just to capture that effect - simply chaining
suspend
calls won't do that?
p
you only need IO at the topmost level
if that’s the case
IO.effect { } wraps the whole thing and you run it with
.handleErrorWith { }
at whatever level you need
b
Agree on
.handleErrorWith
, but if my route handler accesses (e.g.) databases or files or network calls I still
.flatMap
over
(SomeResource) -> IO<SomeResult>
, I think?
(or
F<SomeResult>
for polymorphic effect types)
p
if you’re not doing anything specially interesting with IO types (threads, parallelism, races) then suspend functions should suffice
chaining suspend functions is equivalent to flatMapping IOs
IO gives you the extra features, those I mentioned, cancellacion, and wrapping existing APIs
you can await on an IO in any suspend function using
myIo.suspended()
starting in 0.10
I need to sit down and write an article, it’s been a long while since I’ve done it
b
I need to stop trying to learn three different implementations of tagless final simultaneously
p
that works too 😄
scala, haskell, arrow?
b
yup
most activity I see is in the cats ecosystem gitters, so translating from tagless final using implicits without coroutines to explicits with suspend in Kotlin is overwhelming at times
I'll admit that I don't fully understand yet why suspend... is isomorphic to IO. I probably need to re-read the papers and the Arrow code again
p
Read the implementation of the Async typeclass
all asynchronous frameworks are built on the same principle, no matter how obfuscated
so you can pipe them together in a generic way
They’re constructed for asynchronous calls Async.async { callback -> // same for IO.async } Observable.create { emitter -> } suspendCoroutine { continuation -> }
and then they’re executed on a non-blocking way: execute(new Listener { @Override onSuccess() { } @Override onFailure() { } }) subscribe(new SingleObserver { @Override onNext() { } @Override onFailure() { } }) unsafeRunAsync { it.fold( { error -> }, { success -> } ) } launch { try { // Success } catch (t: Throwable) { // Error } }
and then it’s mix and match
Observable.create { emitter -> launch { try { emitter.onNext(operation()) } catch (t: Throwable) { emitter.onError(t) } } }
suspendCoroutine { continuation -> unsafeRunAsync { it.fold( { error -> continuation.resume(Failure(error)) }, { success -> continuation.resume(Success(success)) } ) } }
if they’re cancellable they provide a way of checking a flag on the launch/execute/subscribe or it returns a subscription on the other side, the async constructor can provide a way of putting lambdas that’ll execute on completion, like emitter.setCancellable or launch.runAfterCompletion or whatever is called
so you call that subscription on the lambda
because we don’t want to pull the whole kx.coroutines we wrote a small version of
launch
that does cancellation without all the bells and whistles, and is piped to IO
that bad boy
b
Thanks, I'll read through those! I think my choice of the word isomorphic (between IO and suspend) was poor, though. I think what I was really trying to understand was the bridge between
suspend
and
IO
, and (I think) that 0.10.x has methods that go from IO -> suspend and from suspend -> IO. So I think that means with 0.10.x that if ktor handlers are suspend, I can supply an
IO {...}.handleErrorWith {...}.suspended()
(or whatever the IO -> suspend bridge method is called) and hand that right to ktor. And if I'm understanding you right I can interleave IO and suspend as appropriate in a similar fashion
arrow 2
s
Your understanding is correct