Tiberiu Tofan
03/30/2022, 11:11 AMeither {}
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?simon.vergauwen
03/30/2022, 11:18 AMeither
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.Tiberiu Tofan
03/30/2022, 11:24 AMeither.eager
is pure because it doesn’t allow non-pure code, as all the non-pure code is suspended? (by convention)Pavel
03/30/2022, 11:26 AMeither.eager
is pure i.e. when you trigger it with the same input you always get the same outputsimon.vergauwen
03/30/2022, 11:27 AMas 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
Tiberiu Tofan
03/30/2022, 11:27 AMeither {}
pure no matter of the code inside?Pavel
03/30/2022, 11:29 AMTiberiu Tofan
03/30/2022, 11:29 AMTiberiu Tofan
03/30/2022, 11:30 AMPavel
03/30/2022, 11:31 AMPavel
03/30/2022, 11:32 AMTiberiu Tofan
03/30/2022, 11:33 AMPavel
03/30/2022, 11:37 AMsimon.vergauwen
03/30/2022, 11:41 AMIO
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.simon.vergauwen
03/30/2022, 11:41 AMTiberiu Tofan
03/30/2022, 11:42 AMsimon.vergauwen
03/30/2022, 11:42 AMTiberiu Tofan
03/30/2022, 11:42 AMsimon.vergauwen
03/30/2022, 11:44 AMsuspend
we can do FP in Kotlin without much of its overhead or learning curve. At least I think so 😄Pavel
03/30/2022, 11:49 AMsuspend
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)Pavel
03/30/2022, 11:51 AMsuspend
? (beside knowing that they have side-effects)Tiberiu Tofan
03/30/2022, 11:52 AMTiberiu Tofan
03/30/2022, 11:54 AMPavel
03/30/2022, 11:54 AMTiberiu Tofan
03/30/2022, 11:58 AMCompletableFuture<T>
or Deferred<T>
(if you’re stuck with Spring MVC) and switch your code to async.Pavel
03/30/2022, 11:59 AMrunBlocking
? (in spring-boot, I am stuck with spring boot at this point)Tiberiu Tofan
03/30/2022, 12:01 PMrunBlocking
would defeat the purpose and block the caller threadTiberiu Tofan
03/30/2022, 12:07 PMPavel
03/30/2022, 12:13 PMI’m also really curious on @simon.vergauwen opinion on itme 2 😄
simon.vergauwen
03/30/2022, 12:16 PMsuspend
. 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.simon.vergauwen
03/30/2022, 12:16 PMTiberiu Tofan
03/30/2022, 12:43 PMTiberiu Tofan
03/30/2022, 12:43 PM@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
}
Tiberiu Tofan
03/30/2022, 12:46 PMevalOn(..)
be similar?Tiberiu Tofan
03/30/2022, 12:47 PMsimon.vergauwen
03/30/2022, 1:28 PMevalOn(..)
hasn't existed anymore in some time.simon.vergauwen
03/30/2022, 1:28 PMRequestScope
seems dangerous though. I've used https://github.com/joost-de-vries/spring-coroutine which offers a CoroutineScope through a destroyable bean.Tiberiu Tofan
03/30/2022, 1:37 PMPavel
03/30/2022, 1:55 PMIt allows you to do things such asWhat I struggle to get is -> when each request in Spring MVC Boot is handled by 1 thread from start to finish (to wrap database calls. Not sure what you mean bywithContext(<http://Dispatchers.IO|Dispatchers.IO>)
, 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.one-thread-per-request model
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 🙂simon.vergauwen
03/30/2022, 2:05 PMForkJoinPool
, 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.Pavel
03/30/2022, 2:08 PMsimon.vergauwen
03/30/2022, 2:09 PMPavel
03/30/2022, 2:18 PMTiberiu Tofan
03/30/2022, 4:20 PMJob
with SupervisorJob
fixes this, and I think it’s a better default.
This is my playground: https://github.com/tibtof/spring-mvc-coroutines-examplePavel
03/30/2022, 4:46 PMPavel
04/01/2022, 8:29 AMTiberiu Tofan
04/01/2022, 9:29 AMPavel
04/01/2022, 10:26 AMsimon.vergauwen
04/01/2022, 10:53 AMPavel
04/01/2022, 4:57 PMCoroutineScope
(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):
@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?simon.vergauwen
04/01/2022, 5:45 PMCoroutineScope
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.simon.vergauwen
04/01/2022, 5:47 PM<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
Pavel
04/01/2022, 5:55 PMCoroutineScope
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?Tiberiu Tofan
04/01/2022, 6:05 PMTiberiu Tofan
04/01/2022, 6:07 PMTiberiu Tofan
04/01/2022, 6:08 PMPavel
04/01/2022, 6:19 PMDeferredResult
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 😀.