Suggestion: Scoped(multi-scope) Coroutines
See original GitHub issueHi, I’ve read interesting blog post about how monads and its compositions can be expressed with coroutines only - http://blog.paralleluniverse.co/2015/08/07/scoped-continuations/
As I understood post talks about how coroutines can be used to chain/compose monad usages. One real example would be: database access and async task. Now in ktor if we want to use transactional database queries correctly we need explicitly pass transaction managers to all suspend functions which is created in route action closure. I’m thinking maybe we could set that transaction manager inside coroutineContext`s property and use that transaction manager in all suspend functions implicitly by reading it from coroutineContext. But would be great that type system would support some checking. For example:
suspend<DbTransaction> fun queryDatabase() = coroutineScope {
val dbTransaction = coroutineContext[DbTransactionKey]
}
suspend<DbTransaction, CurrentUser> fun doPurchase() = coroutineScope {
val dbTransaction = coroutineContext[DbTransactionKey]
val currentUser = coroutineContext[CurrentUserKey]
}
// to create scope with some coroutine builder
fun withDatabaseTransaction(dbTransaction, block) = async<DbTransaction> {
coroutineContext[DbTransactionKey] = dbTransaction
block()
}
fun withCurrentUser(currentUser, block) = async<CurrentUser> {
coroutineContext[CurrentUserKey] = dbTransaction
block()
}
// usage:
fun httpRouteAction(request) {
withDatabaseTransaction(db) {
withCurrentUser(request.authUser) {
someAsyncFunctionWhichUseesDoPurchaseSomewhereDeep()
}
}
}
As of writing I understand that type system probably couldn’t determine if call site is inside both scopes during compilation and maybe this is more of implicitly imported variables not coroutines, but I’m interested of practices and thoughts how to deal with a given situation. Thanks.
Issue Analytics
- State:
- Created 5 years ago
- Comments:10 (6 by maintainers)
It is a nice article (and the accompanying talk), but I don’t see much pragmatic value in the concept of this kind of delimited (and/or scoped) continuations. Regular function parameters and receiver types in Kotlin address the need to represent the context required for the function call in its type quite well. I don’t see why would we need yet another mechanism to the same goal. Frankly, I don’t see how:
is better than (just as one example to write it):
However, if you’ve got interested in
shift
/reset
-style delimited continuations, then you are welcome to study and use the implementation from this gist which is completely type-safe and actually represents a variant of scoped continuation implementation (the scope is in the receiver position): https://gist.github.com/elizarov/5bbbe5a3b88985ae577d8ec3706e85efMy implementation of
shift
/reset
is delimited (which implies that it has multiple-prompts). The reference toDelimitedScope
thatreset
provides to the inner block is its prompt tag. Theshift
is invoked on a particularDelimitedScope
instance and that determines which “slice of execution stack” is captured and suspended at this point.However, this has little pragmatic use for a language like Kotlin, since any concept that might be possible to implement via delimited continuations in otherwise purely functional language is already supported by Kotlin natively, naturally, and efficiently. There is little need to reimplement them via delimited continuations.