Stacks for Kotlin: Design Notes #486
Replies: 21 comments 41 replies
|
Apologies, I haven't fully read the proposal. I'm somewhat struggling to understand the motivation and purpose behind this feature. It's my understanding that, while |
|
The entire Flow section should probably be moved to the local lifetimes notes, since it doesn't use anything related to the stacks. |
|
It would be nice if lifetime members get introduced in the local lifetime notes. They could be an important point of discussion. |
|
For |
|
fun <E, local_{resumption} arena, local that> mount(
mount: StackMount<arena, E>_{local},
environment: E_{resumption},
suspension: StackSuspension<that>_{mount.mounted},
block: local StackRestacker<that>.() ->_{that} Nothing
): Nothing |
|
This echoes a question I asked on the lifetimes notes. Currently, fun restack(once block: local StackRestacker<local>.() -> R): Rbut could it also be defined as: fun restack(once block: local StackRestacker<block>.() -> R): Rand what would be the differences, if any? |
|
Is all the environment juggling necessary for this design? It seems like environments could simply be a reference that gets set on the mount. Perhaps lifetime safety necessitates such juggling? |
|
Can't dismount be simplified to the following? fun <local_{resumption} arena> dismount(
mount: StackMount<arena, E, mounted_{resumption}>_{local},
block: local^{environment} StackRestacker<environment>.(
local^{arena} environment: E,
StackSuspension<resumption>_{mount.mounted}
) -> Nothing
): Nothingi.e. because |
|
Can you elaborate a bit on why |
|
It seems to me that if you remove all the lifetimes and add |
|
With this and lifetimes, is |
|
Can public fun <T> sequence(local_{} block: local SequenceScope<T, block>.() -> Unit): Sequence<T>_{block}Note the lower-bound on On the other side, public fun <T> iterator(
local_{} block: local SequenceScope<T, block>.() -> Unit
): Iterator<T>_{block}So that one can make an |
I don't think that's true! I'll focus on JVM here, but I'm very sure that this applies to all platforms. val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
In fact, your public fun <T> createCoroutineUnintercepted(suspendingBlock: suspend (T) -> Unit, completion: Continuation<Unit>): Continuation<T>Technically, you can throw an exception at the site of |
That's not true! I'm pretty sure 3 and 4 happen before 2. If you throw an exception within |
|
A few tiny nits for the code (I found these by removing lifetimes and trying to compile the code). Some of them do affect understanding, while others are just compiler technicalities:
= new {
val output = block()
restack {
dismount(this@new) { environment, _ ->
fun <local_{local} arena, E, R> StackMount<arena, E>.resume(
...
): Nothing {
restack {
mount(environment, this@resume, continuation.suspension) {Similarly for fun_{mounted} <local arena, E, R> StackMount<arena, E>.suspend(
...
): Nothing {
restack {
dismount(this@suspend) { environment, suspension ->
local class PausingStack(
private val block: (local pause: () -> Unit) ->_{this} Unit
) {
private[this] val mount: StackMount<this, (Boolean) -> Nothing>
= StackMount()
private[this] var continuation: StackContinuation<() -> Nothing>_{mount.mounted}?
= mount.new({ exit, _ -> exit(true) }) {
block pause@{
mount.suspend({ return@pause }) { exit, continuation ->
this.continuation = continuation
exit(false)
}
}
}
fun progress(): Boolean {
mount.resume(
{ return@progress it },
(continuation ?: return true).also { continuation = null }
) {
it()
}
}
}
Also,
There's also a little bit of strange formatting, like splitting extension function types with empty parameters (see e.g. |
|
Do you have any examples of why that can be useful? I don't think an analogy exists with other systems that I've seen. |
|
Does // convenience function to create a mount, create a new continuation from it, and resume it
fun newMount(block: (mount: StackMount<local, Unit>) ->_{local&mount.mounted} Unit): Unit = with(StackMount()) {
resume(Unit, new(Unit)) { return block() }
}
newMount { m1 ->
newMount { m2 ->
m1.suspend(Unit) { _, _ ->
m1.resume(Unit, new(Unit)) { // setting it up again so that the arena for `m2` is satisfied
m2.resume(Unit, new(Unit)) { ... } // does this result in an exception?
}
}
}
}If true, I don't see how the dynamic checks for |
|
|
|
I don't think this is valid. |
|
This is somewhat tangential to the proposal, but I've been thinking about ways to have pausable and resumable computations that survive process boundaries (i.e. can be serialized and persisted). This doesn't seem like it would help directly, although I'd probably want to use the "another kind of suspend" composability support and being able to inspect |
Uh oh!
There was an error while loading. Please reload this page.
This is a discussion of the design notes for Stacks for Kotlin. These notes summarize ongoing research on a library providing primitives for creating and working with first-class call-stacks, a feature that provides safer and more compositional support for coroutines. Note that this work heavily relies on local lifetimes, so the reader is advised to first familiarize themselves with that research before proceeding with these notes.
Disclaimer: This work is still research in progress. We are sharing it early because we want to collect feedback (hopes, concerns, suggestions, and so on) from the community before determining whether to develop it into a formal proposal.
All reactions