Generics for suspend: Reuse suspend code in normal code and vice versa


#1

Consider the following sync and async interfaces:

interface Out {
    fun write(data: Byte)
}

interface AsyncOut {
    suspend fun write(data: Byte)
}

Both interface have the same semantics, only the functions in AsyncOut are suspend functions. Is there a generic way to have a class or a function that works with both interfaces? e.g.

fun writeAll(out: Out|AsyncOut)

with out either the Out or the AsyncOut interface. In the later case the writeAll function would have to becomes a suspend function. The writeAll method would be exactly the same otherwise.

Is there a way to reuse the code in writeAll to work with suspend and non-suspend interfaces?


#2

At a glance i’d concentrate not on interfaces but on the Coroutine since it accepts both suspend and non-suspend code blocks. In particular function writeAll might look like this:

fun writeAll(code: suspend (Data) -> Unit): CompletableFuture<Boolean> {
	val future = CompletableFuture<Boolean>()
	code.startCoroutine(object : Continuation<Boolean> {
		override fun resume(value: Boolean) { future.complete(true) }
		...
	})
	return future
}

And can be used like this:

fun testSync(writer: Out) {
	writeAll{data -> writer.write(data)}
}
fun testAsync(writer: AsyncOut) {
	writeAll{data -> writer.write(data)}
}

Not the most efficient implementation of the idea, but clear enough to catch it.


#3

In general, once you entered the asynchronous world there is no way back to the synchronous world. For example, there are no threads in js and thus there is no join() or get() equivalent in js. So this is really about reusing existing code. At the moment I simply copied my code and just changed the interface and added suspend key words. This works fairly well but you get a lot of code duplication.

A possible API could look like that:

interface<T: suspend> Out {
    T fun write(data: Byte)
}

typealias SyncOut = Out
typealias AsyncOut = Out<suspend>

T fun <T: suspend>writeAll(out: Out<T>)

#4

AFAIK Java and Kotlin support structural generalization, i.e. "what"s. However you’re talking about behavioral generalization, i.e. "how"s, that doesn’t seem possible in these languages. I believe reconsidering your approach would be the good idea.


#5

There is currently no way to transparently reuse code if the way you want it. We did consider supporting that in the language, but an experience of working with actual code had shown that the code that is working with suspending functions and APIs is usually structured differently for various reasons than the corresponding code that is working with regular / blocking APIs. For example, when you write your blocking code it is normal to write data byte-by-byte as you’ve presented in the example of Out interface, however the asynchronous code is usually structured on a higher-level, working with larger domain-oriented message and, for performance reasons, its lower-level are structured different on top of the corresponding asynchronous IO APIs. So, while we are working on the ByteSendChannel interface as a part of kotlinx-coroutines-io module and it seems kind of a “suspending version” of OutputStream, you will notice that it has a number of differences in its design.