Skip to content

Commit

Permalink
Backport flattening token/shift into Cont (#2791)
Browse files Browse the repository at this point in the history
* Flatten Token, and Shift backport
* Switch when
  • Loading branch information
nomisRev committed Aug 8, 2022
1 parent 2c37ca1 commit b6be54f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 37 deletions.
18 changes: 16 additions & 2 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Expand Up @@ -2720,11 +2720,25 @@ public final class arrow/core/continuations/EffectScopeKt {
public static final fun ensureNotNull (Larrow/core/continuations/EffectScope;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/FoldContinuation : kotlin/coroutines/Continuation {
public final class arrow/core/continuations/FoldContinuation : arrow/core/continuations/Token, arrow/core/continuations/EffectScope, kotlin/coroutines/Continuation {
public field recover Lkotlin/jvm/functions/Function2;
public fun <init> (Larrow/core/continuations/Token;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)V
public fun <init> (Larrow/core/continuations/Token;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
public fun <init> (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Validated;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/continuations/EagerEffect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/continuations/Effect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun catch (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun ensure (ZLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun getContext ()Lkotlin/coroutines/CoroutineContext;
public final fun getRecover ()Lkotlin/jvm/functions/Function2;
public fun resumeWith (Ljava/lang/Object;)V
public final fun setRecover (Lkotlin/jvm/functions/Function2;)V
public fun shift (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/IorEagerEffectScope : arrow/core/continuations/EagerEffectScope, arrow/typeclasses/Semigroup {
Expand Down Expand Up @@ -3004,7 +3018,7 @@ public final class arrow/core/continuations/Suspend : arrow/core/continuations/S
public fun toString ()Ljava/lang/String;
}

public final class arrow/core/continuations/Token {
public class arrow/core/continuations/Token {
public fun <init> ()V
public fun toString ()Ljava/lang/String;
}
Expand Down
Expand Up @@ -693,7 +693,7 @@ internal class Suspend(val token: Token, val shifted: Any?, val recover: suspend

/** Class that represents a unique token by hash comparison **/
@PublishedApi
internal class Token {
internal open class Token {
override fun toString(): String = "Token(${hashCode().toString(16)})"
}

Expand All @@ -709,36 +709,57 @@ internal class Token {
"This will become private in Arrow 2.0, and is not going to be visible from binary anymore",
level = DeprecationLevel.WARNING
)
internal class FoldContinuation<B>(
private val token: Token,
internal class FoldContinuation<R, B>(
override val context: CoroutineContext,
private val error: suspend (Throwable) -> B,
private val parent: Continuation<B>
) : Continuation<B> {
private val parent: Continuation<B>,
) : Continuation<B>, Token(), EffectScope<R> {

constructor(token: Token, context: CoroutineContext, parent: Continuation<B>) : this(token, context, { throw it }, parent)
constructor(ignored: Token, context: CoroutineContext, parent: Continuation<B>) : this(context, { throw it }, parent)
constructor(
ignored: Token,
context: CoroutineContext,
error: suspend (Throwable) -> B,
parent: Continuation<B>,
) : this(context, error, parent)

lateinit var recover: suspend (R) -> Any?

// Shift away from this Continuation by intercepting it, and completing it with
// ShiftCancellationException
// This is needed because this function will never yield a result,
// so it needs to be cancelled to properly support coroutine cancellation
override suspend fun <B> shift(r: R): B =
// Some interesting consequences of how Continuation Cancellation works in Kotlin.
// We have to throw CancellationException to signal the Continuation was cancelled, and we
// shifted away.
// This however also means that the user can try/catch shift and recover from the
// CancellationException and thus effectively recovering from the cancellation/shift.
// This means try/catch is also capable of recovering from monadic errors.
// See: EffectSpec - try/catch tests
throw Suspend(this, r, recover as suspend (Any?) -> Any?)

// In contrast to `createCoroutineUnintercepted this doesn't create a new ContinuationImpl
private fun <A> (suspend () -> A).startCoroutineUnintercepted(cont: Continuation<A>): Unit {
private fun (suspend () -> B).startCoroutineUnintercepted() {
try {
when (val res = startCoroutineUninterceptedOrReturn(cont)) {
when (val res = startCoroutineUninterceptedOrReturn(parent)) {
COROUTINE_SUSPENDED -> Unit
else -> cont.resume(res as A)
else -> parent.resume(res as B)
}
// We need to wire all immediately throw exceptions to the parent Continuation
} catch (e: Throwable) {
cont.resumeWithException(e)
parent.resumeWithException(e)
}
}

override fun resumeWith(result: Result<B>) {
result.fold(parent::resume) { throwable ->
when {
throwable is Suspend && token == throwable.token ->
suspend { throwable.recover(throwable.shifted) as B }.startCoroutineUnintercepted(parent)
throwable is Suspend && this === throwable.token ->
suspend { throwable.recover(throwable.shifted) as B }.startCoroutineUnintercepted()

throwable !is Suspend -> suspend { error(throwable.nonFatalOrThrow()) }.startCoroutineUnintercepted(parent)
else -> parent.resumeWith(result)
throwable is Suspend -> parent.resumeWith(result)
else -> suspend { error(throwable.nonFatalOrThrow()) }.startCoroutineUnintercepted()
}
}
}
Expand Down Expand Up @@ -790,29 +811,13 @@ private class DefaultEffect<R, A>(val f: suspend EffectScope<R>.() -> A) : Effec
transform: suspend (value: A) -> B,
): B =
suspendCoroutineUninterceptedOrReturn { cont ->
val token = Token()
val effectScope =
object : EffectScope<R> {
// Shift away from this Continuation by intercepting it, and completing it with
// ShiftCancellationException
// This is needed because this function will never yield a result,
// so it needs to be cancelled to properly support coroutine cancellation
override suspend fun <B> shift(r: R): B =
// Some interesting consequences of how Continuation Cancellation works in Kotlin.
// We have to throw CancellationException to signal the Continuation was cancelled, and we
// shifted away.
// This however also means that the user can try/catch shift and recover from the
// CancellationException and thus effectively recovering from the cancellation/shift.
// This means try/catch is also capable of recovering from monadic errors.
// See: EffectSpec - try/catch tests
throw Suspend(token, r, recover as suspend (Any?) -> Any?)
}

val shift = FoldContinuation<R, B>(cont.context, error, cont)
shift.recover = recover
try {
suspend { transform(f(effectScope)) }
.startCoroutineUninterceptedOrReturn(FoldContinuation(token, cont.context, error, cont))
val fold: suspend EffectScope<R>.() -> B = { transform(f(this)) }
fold.startCoroutineUninterceptedOrReturn(shift, shift)
} catch (e: Suspend) {
if (token == e.token) {
if (shift === e.token) {
val f: suspend () -> B = { e.recover(e.shifted) as B }
f.startCoroutineUninterceptedOrReturn(cont)
} else throw e
Expand Down

0 comments on commit b6be54f

Please sign in to comment.