Satyam Agarwal
08/03/2020, 11:06 AMIO<A>. I thought of starting with this line :
IO<A> is just a another way of writing suspend () -> Either<Throwable, A>
is this true ? I grasped this from the PR Simon worked on to bring features from IO to Either using coroutines so that we don’t have to box our types which apparently is costly.
I know IO has its own thread pool, works on fibres and not coroutines, but I was wondering if the above statement could be a layman’s explanation ?simon.vergauwen
08/03/2020, 12:05 PMIO<A> ~> suspend () -> A, since suspend encapsulates Throwable.
If you try to run suspend () -> A, you always need to provide a function (Result<A>) -> Unit in startCoroutine when constructing a Continiuation<A>.
Similarly to IO which exposes a function unsafeRunAsync which takes a function (Either<Throwable, A>) -> Unit.
So it's clear these two are the same.
Building IO is more expensive than using suspend since IO is build at runtime, and suspend is generated by the compiler at compile time. This costs becomes neglectable in heavy concurrent applications with a lot of IO operations, and it becomes more expensive in loops for example.Satyam Agarwal
08/03/2020, 12:07 PMsimon.vergauwen
08/03/2020, 12:07 PMIO module exposes a set of utilities like Fiber, Resoure, Schedule, dispatchers. and much more to work with IO.
In the last year I've been working hard on trying to figure out how we could translate all these same principles and patterns from IO to suspend without losing any features or capabilities. Which finally landed in the PR you probably saw already.
The same readme can be found here: https://github.com/arrow-kt/arrow-fx/tree/master/arrow-fx-coroutinessimon.vergauwen
08/03/2020, 12:08 PMSatyam Agarwal
08/03/2020, 12:12 PMsimon.vergauwen
08/03/2020, 12:14 PMSatyam Agarwal
08/03/2020, 12:15 PMfun Application.myModule() : Unit to suspend fun <http://Application.my|Application.my> module() : Unit { ... myUnsafeIOWrapped.suspendedCancellable() } Also he asked about blocking and non-blocking.simon.vergauwen
08/03/2020, 12:31 PMThread.sleep which actually block a thread.
Which is problematic on a pool such as ComputationPool, IO.dispatchers().default() or Dispatchers.Default, since those pools are "work-stealing". Meaning they interleave computational work, which is not possible if an operation is blocking the thread.
(TL;DR Blocking doesn't allow interleaving of tasks)
So a blocking task should be scheduled on an "IO" pool, which creates a new thread per operation. Hence blocking is not a problem, since no interleaving happens on this pool. IOPool, IO.dispatchers().io(), <http://Dispatchers.IO|Dispatchers.IO>, etc.
The part about IO and suspend could be argued about perhaps, since on a back-end requests are running in parallel there is always asynchrony involved. What makes both IO and suspend so special is that they can wrap a callback which regular code cannot.
Being able to wrap a callback allows you to jump to a different scheduler, and back, without having to write any of the orchestration around it like you normally would with callbacks.
val result: Result? = ...
runOnComputation { result ->
runOnIO(result) { res2 ->
runOnComputation(res) { res3 ->
finished, return result (???)
result = res3
}
}
}
use result??
compared to
val res = runOnComputation()
val res2 = runOnIO(res)
val res3 runOnComputation(res2)
Where you can implement runComputation with suspendCoroutine like you could do with IO.async.simon.vergauwen
08/03/2020, 12:34 PMparTraverse or parTupled or run them synchronously depending on the config we passed to Ktor.
Similarly you can make functions uncancellable by wrapping them in uncancellable { }. Even if they contain cancellable code.Satyam Agarwal
08/03/2020, 12:39 PM