diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffect.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffect.kt index 557b398e672..3f24402fe05 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffect.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffect.kt @@ -161,41 +161,42 @@ internal class Eager(val token: Token, val shifted: Any?, val recover: (Any?) -> * ``` * */ -public inline fun eagerEffect(crossinline f: suspend EagerEffectScope.() -> A): EagerEffect = - object : EagerEffect { - override fun fold(recover: (R) -> B, transform: (A) -> B): B { - val token = Token() - val eagerEffectScope = - object : EagerEffectScope { - // 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 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: EagerEffectSpec - try/catch tests - throw Eager(token, r, recover as (Any?) -> Any?) - } - - return try { - suspend { transform(f(eagerEffectScope)) } - .startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { result -> - result.getOrElse { throwable -> - if (throwable is Eager && token == throwable.token) { - throwable.recover(throwable.shifted) as B - } else throw throwable - } - }) as B - } catch (e: Eager) { - if (token == e.token) e.recover(e.shifted) as B - else throw e +public fun eagerEffect(f: suspend EagerEffectScope.() -> A): EagerEffect = DefaultEagerEffect(f) + +private class DefaultEagerEffect(private val f: suspend EagerEffectScope.() -> A) : EagerEffect { + override fun fold(recover: (R) -> B, transform: (A) -> B): B { + val token = Token() + val eagerEffectScope = + object : EagerEffectScope { + // 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 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: EagerEffectSpec - try/catch tests + throw Eager(token, r, recover as (Any?) -> Any?) } + + return try { + suspend { transform(f(eagerEffectScope)) } + .startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { result -> + result.getOrElse { throwable -> + if (throwable is Eager && token == throwable.token) { + throwable.recover(throwable.shifted) as B + } else throw throwable + } + }) as B + } catch (e: Eager) { + if (token == e.token) e.recover(e.shifted) as B + else throw e } } +} private const val deprecateMonadAppFunctorOperators: String = "Operators related to Functor, Applicative or Monad hierarchies are being deprecated in favor of bind" diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt index aefc254347d..ee493d3a488 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/Effect.kt @@ -752,38 +752,39 @@ internal class FoldContinuation( * ``` * */ -public inline fun effect(crossinline f: suspend EffectScope.() -> A): Effect = - object : Effect { - // We create a `Token` for fold Continuation, so we can properly differentiate between nested - // folds - override suspend fun fold(recover: suspend (R) -> B, transform: suspend (A) -> B): B = - suspendCoroutineUninterceptedOrReturn { cont -> - val token = Token() - val effectScope = - object : EffectScope { - // 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 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?) - } +public fun effect(f: suspend EffectScope.() -> A): Effect = DefaultEffect(f) - try { - suspend { transform(f(effectScope)) } - .startCoroutineUninterceptedOrReturn(FoldContinuation(token, cont.context, cont)) - } catch (e: Suspend) { - if (token == e.token) { - val f: suspend () -> B = { e.recover(e.shifted) as B } - f.startCoroutineUninterceptedOrReturn(cont) - } else throw e +private class DefaultEffect(private val f: suspend EffectScope.() -> A) : Effect { + // We create a `Token` for fold Continuation, so we can properly differentiate between nested + // folds + override suspend fun fold(recover: suspend (R) -> B, transform: suspend (A) -> B): B = + suspendCoroutineUninterceptedOrReturn { cont -> + val token = Token() + val effectScope = + object : EffectScope { + // 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 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?) } + + try { + suspend { transform(f(effectScope)) } + .startCoroutineUninterceptedOrReturn(FoldContinuation(token, cont.context, cont)) + } catch (e: Suspend) { + if (token == e.token) { + val f: suspend () -> B = { e.recover(e.shifted) as B } + f.startCoroutineUninterceptedOrReturn(cont) + } else throw e } - } + } +} diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/either.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/either.kt index c4e58d5a14c..a03b738ef37 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/either.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/either.kt @@ -4,9 +4,9 @@ import arrow.core.Either @Suppress("ClassName") public object either { - public inline fun eager(crossinline f: suspend EagerEffectScope.() -> A): Either = + public inline fun eager(noinline f: suspend EagerEffectScope.() -> A): Either = eagerEffect(f).toEither() - public suspend inline operator fun invoke(crossinline f: suspend EffectScope.() -> A): Either = + public suspend operator fun invoke(f: suspend EffectScope.() -> A): Either = effect(f).toEither() }