diff --git a/arrow-libs/core/arrow-core/build.gradle.kts b/arrow-libs/core/arrow-core/build.gradle.kts index 279f19ef2c1..2e616680e98 100644 --- a/arrow-libs/core/arrow-core/build.gradle.kts +++ b/arrow-libs/core/arrow-core/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { id(libs.plugins.kotlin.multiplatform.get().pluginId) alias(libs.plugins.arrowGradleConfig.kotlin) @@ -40,3 +42,8 @@ kotlin { } } } + +// enables context receivers for Jvm Tests +tasks.named("compileTestKotlinJvm") { + kotlinOptions.freeCompilerArgs += "-Xcontext-receivers" +} 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 b140456c51a..6af3702ba07 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 @@ -18,7 +18,7 @@ import kotlin.coroutines.RestrictsSuspension * An [effect] computation interoperates with an [EagerEffect] via `bind`. * @see Effect */ -public interface EagerEffect { +public interface EagerEffect { /** * Runs the non-suspending computation by creating a [Continuation] with an [EmptyCoroutineContext], @@ -91,12 +91,12 @@ public interface EagerEffect { * [fold] the [EagerEffect] into an [Option]. Where the shifted value [R] is mapped to [Option] by the * provided function [orElse], and result value [A] is mapped to [Some]. */ - public fun toOption(orElse: (R) -> Option): Option = + public fun toOption(orElse: (R) -> Option<@UnsafeVariance A>): Option = fold(orElse, ::Some) public fun map(f: (A) -> B): EagerEffect = flatMap { a -> eagerEffect { f(a) } } - public fun flatMap(f: (A) -> EagerEffect): EagerEffect = eagerEffect { + public fun flatMap(f: (A) -> EagerEffect<@UnsafeVariance R, B>): EagerEffect = eagerEffect { f(bind()).bind() } @@ -104,11 +104,11 @@ public interface EagerEffect { kotlin.runCatching { bind() } } - public fun handleError(f: (R) -> A): EagerEffect = eagerEffect { + public fun handleError(f: (R) -> @UnsafeVariance A): EagerEffect = eagerEffect { fold(f, ::identity) } - public fun handleErrorWith(f: (R) -> EagerEffect): EagerEffect = + public fun handleErrorWith(f: (R) -> EagerEffect): EagerEffect = eagerEffect { toEither().fold({ r -> f(r).bind() }, ::identity) } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffectScope.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffectScope.kt index ffafc43cb3f..a0be9e206bf 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffectScope.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffectScope.kt @@ -13,7 +13,7 @@ import kotlin.coroutines.RestrictsSuspension /** Context of the [EagerEffect] DSL. */ @RestrictsSuspension -public interface EagerEffectScope { +public interface EagerEffectScope { /** Short-circuit the [EagerEffect] computation with value [R]. * 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 ed311c6cd60..aefc254347d 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 @@ -568,7 +568,7 @@ import kotlin.coroutines.resume * ``` * */ -public interface Effect { +public interface Effect { /** * Runs the suspending computation by creating a [Continuation], and running the `fold` function * over the computation. @@ -637,7 +637,7 @@ public interface Effect { * [fold] the [Effect] into an [Option]. Where the shifted value [R] is mapped to [Option] by the * provided function [orElse], and result value [A] is mapped to [Some]. */ - public suspend fun toOption(orElse: suspend (R) -> Option): Option = fold(orElse, ::Some) + public suspend fun toOption(orElse: suspend (R) -> Option<@UnsafeVariance A>): Option = fold(orElse, ::Some) /** * [fold] the [Effect] into an [A?]. Where the shifted value [R] is mapped to @@ -654,11 +654,11 @@ public interface Effect { } } - public fun handleError(recover: suspend (R) -> A): Effect = effect { + public fun handleError(recover: suspend (R) -> @UnsafeVariance A): Effect = effect { fold(recover, ::identity) } - public fun handleErrorWith(recover: suspend (R) -> Effect): Effect = effect { + public fun handleErrorWith(recover: suspend (R) -> Effect): Effect = effect { fold({ recover(it).bind() }, ::identity) } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt index f13b904c1bb..5a1f7d23d6d 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EffectScope.kt @@ -11,7 +11,7 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract /** Context of the [Effect] DSL. */ -public interface EffectScope { +public interface EffectScope { /** * Short-circuit the [Effect] computation with value [R]. * diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/continuations/EffectUsage.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/continuations/EffectUsage.kt new file mode 100644 index 00000000000..b61c79d6db7 --- /dev/null +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/arrow/core/continuations/EffectUsage.kt @@ -0,0 +1,56 @@ +package arrow.core.continuations + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers + +sealed interface MyError +sealed interface SubError : MyError +sealed interface OtherError : MyError { + object Actual : OtherError +} + +context(EffectScope) + suspend fun subprogram(): Unit = + println("Hello SubProgram!") + +context(EffectScope) + suspend fun otherprogram(): Unit = + println("Hello OtherProgram!") + +context(EffectScope) + suspend fun fail(): MyResponse = + shift(OtherError.Actual) + +fun main() = + runBlocking(Dispatchers.Default) { + effect { + subprogram() + otherprogram() + fail() + }.fold(::println) { } + // Hello SubProgram! + // Hello OtherProgram! + // OtherError$Actual@7cd62f43 + } + +sealed interface MyResponse +object EmptyResponse : MyResponse +data class ErrorResponse(val error: Throwable) : MyResponse +data class BodyResponse(val body: String) : MyResponse + +context(EffectScope) + suspend fun respondWithBody(): BodyResponse = + BodyResponse("Hello Program!") + +context(EffectScope) + suspend fun attemptOrError(): MyResponse = + ErrorResponse(RuntimeException("Oh no!")) + +fun respond(): Effect = + effect { + when (attemptOrError()) { + is BodyResponse -> respondWithBody() + EmptyResponse -> EmptyResponse + is ErrorResponse -> fail() + } + }