Edoardo Luppi
08/20/2023, 8:51 AMfranztesca
08/20/2023, 9:10 AMEdoardo Luppi
08/20/2023, 9:11 AMZDeferred
is a type used to alias CompletableFuture
and Promise
.
It allows me to expose platform-specific async types using interfaces in common code.franztesca
08/20/2023, 9:12 AMEdoardo Luppi
08/20/2023, 9:13 AMEdoardo Luppi
08/20/2023, 9:17 AMfranztesca
08/20/2023, 9:34 AMinterface MyInterface {
suspend fun doSomething(): Int
}
and then you write a separate module. "xyz-platform-export" where you have
interface MyInterface {
fun doSomething(): ZDeferred<Int>
}
Obviously it becomes super cumbersome, but that's how you do it today (that's why I'm upset about kotlin/Js interop). But if you use your ZDeferred
internally you are effectively addressing concurrency and async code in a non-idiomatic way (you are not really using coroutines) and you are also adding differences in the behaviors between different platforms (a Promise
doesn't behave like a CompletableFuture
, there are differences).
What you can do is write a KSP plugin that automatically generates the xzy-platform-export module from the xzy module, removing the need of manual boilerplate, but that is something I'd rather have addresseed by the kotlin/Js compiler directly, rather than needing a third-party KSP cumbersome plugin.Edoardo Luppi
08/20/2023, 10:19 AMMyInterface
needs data passed in by a Java user, or by a JS user, it will need to convert from CompletableFuture
or from Promise
anyway. I get the separation of concerns, but isn't the end result the same?Edoardo Luppi
08/20/2023, 10:22 AMEdoardo Luppi
08/20/2023, 10:33 AMinterface MySuspendingInterface {
suspend fun getValue(): Int
}
And what I expose on the JVM layer:
interface MyInterface {
fun getValue(): ZDeferred<Int>
}
That will simply wrap an instance of MySuspendingInterface
.
Now, a Java consumer can get an instance of MyInterface
using:
MyProducer.getMyInterface(pool: MyDeferredIntPool)
Notice the pool
parameter.
MyDeferredIntPool
needs to be exposed in a way a Java consumer can provide its own, so:
interface MyDeferredIntPool {
fun borrow(): ZDeferred<Int>
}
Now, MySuspendingInterface
also needs to use the passed in MyDeferredIntPool
.
Here I'm forced to use await()
, right?franztesca
08/20/2023, 10:37 AMinterface MyInterface {
fun doSomething(value: suspend () -> Unit)
}
You could still achieve the same with manual conversion:
// in xyz-platform-export
interface MyInterface {
fun doSomething(value: Promise<Unit>)
}
It's true that converting a Promise to a suspend function would have a different behavior, since the Promise is eagerly started, but would limit that change to the wrapper layer. Adding that in the xyz module would instead affect even internal parts.
For example, if you write a suspend function that returns int internally, it's much different from a non-supend function that returns ZDeferred<int>.Edoardo Luppi
08/20/2023, 10:41 AMMySuspendingInterface
will use MyDeferredIntPool
to provide Int
values.
Example:
internal class MySuspendingImpl(private val pool: MyDeferredIntPool) : MySuspendingInterface {
override fun getValue(): Int {
val borrowedValue = pool.borrow() // ZDeferred<Int>
return borrowedValue.await()
}
}
franztesca
08/20/2023, 10:41 AMIn your example, you would have MyIntPool {
suspend fun borrow(): Int
}
In the xyz module and then wiring code in the xyz-platform-export
class MyIntPoolImpl(
private javaPool: MyDeferredIntPool,
): MyIntPool {
suspend fun borrow() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
javaPool.borrow().get()
}
}
franztesca
08/20/2023, 10:42 AMfranztesca
08/20/2023, 10:43 AMEdoardo Luppi
08/20/2023, 10:43 AMIn the xyz module and then wiring code in the xyz-platform-exportThis piece is really what I was looking for. But hopefully the situation is more clear now? Not sure if I made it understandable or not
Edoardo Luppi
08/20/2023, 10:50 AMfranztesca
08/20/2023, 11:01 AMEdoardo Luppi
08/20/2023, 11:06 AMsuspend fun borrow() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
javaPool.borrow().get()
}
Instead of a simpler
suspend fun borrow(): Int =
javaPool.borrow().await()
This would mean the usage context is reusedfranztesca
08/20/2023, 11:12 AMEdoardo Luppi
08/20/2023, 6:55 PMEdoardo Luppi
08/20/2023, 6:57 PMCLOVIS
08/21/2023, 7:30 AMIt allows me to expose platform-specific async types using interfaces in common code.You can't do that, structured concurrency needs some information about the lifetime that isn't available in native types. Otherwise, we'd all use them instead of coroutines… You can't really expose a coroutine as a
Promise
. You have to expose it as a Promise
and a Job
.Edoardo Luppi
08/21/2023, 7:34 AM