Hi everyone, I was looking over the differences be...
# arrow
t
Hi everyone, I was looking over the differences between suspended computation blocks and the eager variant, and my intuition is that the eager variant is not pure. E.g.
either {}
vs
either.eager {}
. I admit that I didn’t fully understand the implementation for the eager variants. Is my assumption correct? Would this be an important argument for using the suspended variant?
s
Hey @Tiberiu Tofan, Both the
either
and
either.eager
variants are pure, but
either { }
requires
suspend
and allows for
suspend
to be called inside the
{ }
. Whilst
either.eager { }
does not require
suspend
and doesn't allow for
suspend
code to be called within the
{ }
. In Kotlin there is a term co-pure which refers to the fact that a HOF is pure but allows for non-pure code to be called inside. So depending on the code inside the
{ }
the usage remains pure or becomes non-pure. That being said, using
suspend
throughout your program to mark side-effects, and non-pure code is promoted by Arrow users/maintainers.
t
So
either.eager
is pure because it doesn’t allow non-pure code, as all the non-pure code is suspended? (by convention)
p
my understanding is that
either.eager
is pure i.e. when you trigger it with the same input you always get the same output
☝️ 1
s
as all the non-pure code is suspended? (by convention)
By convention, so it's not entirely/guaranteed that non-suspending code is pure. I.e. when interoperating with Java SDKs etc
👍 3
t
But isn’t
either {}
pure no matter of the code inside?
p
if you call a higher-order function with mutable state (IO/global variable) it will still remain pure but the output will change since the input has changed
t
The output is not yet evaluated, we always get a continuation
I mean the continuation is the output
p
I don't think that evalution time (eager, lazy ...) affects whether a function is pure. A pure function returns the same output for the same input
FYI @Tiberiu Tofan I really like your blog posts on arrow
👀 1
❤️ 1
t
I think it’s important, because an eager computation returns a computed value (usually), whilst a lazy evaluation returns a way of computing that value, not the value
p
if the function is pure then you get the same output regardless when you evaluate it (assuming the input value does not change during that time). But maybe I am missing some subtle point.
s
Tiberiu is correct in this case. For example when talking to the network or database a function, in that case you want to wrap it in
IO
or in Kotlin
suspend
which is the equivalent of
IO
. This protects you from calling that code from
either.eager
for example, which immediately computes the result.
What is your blog @Tiberiu Tofan?
t
s
t
but I wrote those posts as an introduction to FP, and I avoided FP terms as much as possible
👌 1
s
The goal of Arrow is to avoid FP terms, and its steep learning curve as much as possible. Thanks to
suspend
we can do FP in Kotlin without much of its overhead or learning curve. At least I think so 😄
❤️ 1
👍 1
p
@simon.vergauwen coincidentally I was researching Arrows (I am new to arrow) recommendation on using
suspend
for functions with side effects this weekend and I am not sure it makes sense in applications with one-thread-per-request model (spring-boot to be specific)
do you think it provides any benefit to tag such functions with
suspend
? (beside knowing that they have side-effects)
t
@simon.vergauwen I know you have some great posts on the same topic (domain modeling and validations), I tried to cover some missing points and explain things a bit slower. I will reference your posts in the last article, as they are a bit more advanced.
sorry for derailing this discussion with my blog 🙂
p
no I derailed the discussion before that 😄 ..sorry
😃 1
t
@Pavel most of my work projects are Spring Boot, and we use suspend functions to mark impure code. But you can go one step further and use web-flux or at least return
CompletableFuture<T>
or
Deferred<T>
(if you’re stuck with Spring MVC) and switch your code to async.
p
@Tiberiu Tofan and then you run them with
runBlocking
? (in spring-boot, I am stuck with spring boot at this point)
t
no,
runBlocking
would defeat the purpose and block the caller thread
I will share some code later, I’m also really curious on @simon.vergauwen opinion on it
p
I’m also really curious on @simon.vergauwen opinion on it
me 2 😄
s
@Pavel yes I still think it's useful to mark them as
suspend
. It allows you to do things such as
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
to wrap database calls. Not sure what you mean by
one-thread-per-request model
, Spring Boot has no such limitation. You're still free to do context-switching when necessary for DB calls etc since JDBC works in a blocking way for example. In Spring MVC you can implement a
CoroutineScope
for a Controller and just launch coroutine there or create
future
through KotlinX Coroutines JDK8 support.
If you have any code to share I'd be happy to look at it and give my opinion 😄
t
actually, I forgot, using kotlin-stdlib-jkd8 is almost trivial, initially we implemented our own extensions
Copy code
@RestController
class DemoController {

    //using kotlin-stdlib-jdk8
    @GetMapping("demo")
    fun demo(): CompletableFuture<String> = RequestScope.future {
        "demo"
    }
}

object RequestScope : CoroutineScope {
    override val coroutineContext: CoroutineContext =
        EmptyCoroutineContext + Dispatchers.Default
}
I didn’t use arrow-fx yet; would
evalOn(..)
be similar?
I mean similar in transitioning to coroutines, not in converting to completable future 🙂
s
Arrow Fx Coroutines is fully built on KotlinX since 2 years ago.
evalOn(..)
hasn't existed anymore in some time.
😊 1
That
RequestScope
seems dangerous though. I've used https://github.com/joost-de-vries/spring-coroutine which offers a CoroutineScope through a destroyable bean.
🙌 1
t
Thanks, @simon.vergauwen; I didn’t know of this project. Now I feel stupid for not thinking about the possibility of coroutine leaks; I guess I was lucky not to have problems because of it.
p
@simon.vergauwen
It allows you to do things such as
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
to wrap database calls. Not sure what you mean by
one-thread-per-request model
, Spring Boot has no such limitation. You're still free to do context-switching when necessary for DB calls etc since JDBC works in a blocking way for example.
What I struggle to get is -> when each request in Spring MVC Boot is handled by 1 thread from start to finish (
one-thread-per-request
model) then what is the point of running IO calls in
coroutines
and using for example
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
. Yes you have a way to do parallel computation (IO thread and request thread running at the same time), but other than that I don't see any benefit of
coroutines
for Spring Boot. Sorry for the stupid question, I am slowly introducing Kotlin and Arrow at work, so I am still learning 🙂
s
Requests will typically be handled by
ForkJoinPool
,
CommonPool
,
Dispatchers.Default
or something similar with a fixed-sized pool. So let's say you have a pool size of 10, then you can run 10 requests in parallel. 1 per thread. When processing a request requires talking to the database, you know have a blocking operation that typically takes a significant amount of time. During this time no other requests can be handled. If you offload the blocking database operation to
<http://Dispatchers.IO|Dispatchers.IO>
then you free the thread to handle another request in the meantime. This is unrelated to Kotlin or Coroutines but is how threading on the JVM works. Kotlin Coroutines just offers an extremely easy way of writing such code, in contrast to dealing with ExecutorService, Futures, or Project Reactor.
p
you are absolutely right when using Webflux - it makes sense, but I am not sure you will be able to free up the thread in spring boot MVC using coroutines. I probably need to do more research if this is possible
s
You definitely can when using Spring MVC. This is true for anything on the JVM and is unrelated to MVC vs WebFlux.
p
ah, thanks, I'll try to build a simple example to try it out
t
@Pavel if it helps, I created a very small project to play a bit around with https://github.com/joost-de-vries/spring-coroutine @simon.vergauwen I suspected that because of structured concurrency if one of the coroutines throws an exception the whole scope will be cancelled, and that actually happens. Replacing
Job
with
SupervisorJob
fixes this, and I think it’s a better default. This is my playground: https://github.com/tibtof/spring-mvc-coroutines-example
p
@Tiberiu Tofan thanks! This is what I planned to do, but you beat me to it. Tomorrow I will have a look and play with it.
If the thread-pool used in the coroutineScope is exhausted won't it lead to timeouts for the clients? (from your experience)
t
It depends on what your code does. E.g. if you have blocking operations, switch to Dispatchers.IO
p
that's true, but regardless of dispatcher type (IO or otherwise) it's thread-pool can get exhausted (if enough blocking tasks are started) and then clients will probably start timing out.
s
The Dispatchers.IO is designed specifically for this, if it starts timing out requests that would've also happened with a less optimal solution. @Tiberiu Tofan you're 100% correct. You should use a SupervisorJob for this! Ktor actually also uses a SupervisorJob for this.
👍 2
p
If I wanted to have a request-scoped
CoroutineScope
(if one child fails all should fail) that offloads all computations to
<http://Dispatchers.IO|Dispatchers.IO>
I came up with this pattern (Spring MVC):
Copy code
@PostMapping("/test")
@ResponseStatus(HttpStatus.ACCEPTED)
fun test(): DeferredResult<String> {
    return object : DeferredResult<String>() {
        val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
        var job: Job? = null
    }.apply {
        job = scope.launch {
            val c1 = async { call1() }
            val c2 = async { call2() }
            setResult(call3(c1.await(), c2.await()))
        }
    }
}
Do you see any issues with this? or leaks?
s
That looks very dangerous to me. This
CoroutineScope
will definitely leak. Why not use the solution @Tiberiu Tofan shared? You can also explicitly set that
CoroutineScope
to
<http://Dispatchers.IO|Dispatchers.IO>
if you want.
You could use
<http://Dispatchers.IO|Dispatchers.IO>
instead of
Dispatchers.Default
here. https://github.com/tibtof/spring-mvc-coroutines-example/blob/ea96f0f5e80cefb6eee90[…]n/com/tibtof/springmvccoroutinesexample/SpringCoroutineScope.kt But I would actually not advise it. Ideal scenario is to only run blocking I/O on
IO
Dispatcher. JDBC calls, File I/O, networking, etc. Typically anything that can throw
InterruptedException
or
IOException
p
I liked the solution from Tiberiu, but I was looking for a way to make the lifecycle of
CoroutineScope
shorter than the bean (basically request lifetime i.e. not shared with other requests). What you say about
<http://Dispatcher.IO|Dispatcher.IO>
I agree with fully. I am wondering why will the
CoroutineScope
leak in my example?
t
I'm writing from my phone and it's hard to elaborate, but one reason of leaking could be that Spring will try to cancel the deferred (e.g. connection timeout), but the coroutine will keep computing in vain.
👍 1
The future{} block from my example handles the cancellation of CF and also cancels the coroutine
IMO a supervisor is OK to have a different life cycle and I think you're worried to much there
👍 1
p
The
DeferredResult
is an alternative to the
future
, cancellation can be added (I didn't so that the idea behind the pattern is clearer) that is why I also store
Job
in the
DeferredResult
. My (current) understanding is that
CoroutineScope
was meant to span a computation, tying it to the bean makes it span all computations run within that bean (
controller
). But maybe I am full of BS 😀.