Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: contravariance for R, A in Effect, EagerEffect and in for *EffectScope #2722

Merged
merged 7 commits into from May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions 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)
Expand Down Expand Up @@ -40,3 +42,8 @@ kotlin {
}
}
}

// enables context receivers for Jvm Tests
tasks.named<KotlinCompile>("compileTestKotlinJvm") {
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}
Expand Up @@ -18,7 +18,7 @@ import kotlin.coroutines.RestrictsSuspension
* An [effect] computation interoperates with an [EagerEffect] via `bind`.
* @see Effect
*/
public interface EagerEffect<R, A> {
public interface EagerEffect<out R, out A> {

/**
* Runs the non-suspending computation by creating a [Continuation] with an [EmptyCoroutineContext],
Expand Down Expand Up @@ -91,24 +91,24 @@ public interface EagerEffect<R, A> {
* [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<A>): Option<A> =
public fun toOption(orElse: (R) -> Option<@UnsafeVariance A>): Option<A> =
fold(orElse, ::Some)

public fun <B> map(f: (A) -> B): EagerEffect<R, B> = flatMap { a -> eagerEffect { f(a) } }

public fun <B> flatMap(f: (A) -> EagerEffect<R, B>): EagerEffect<R, B> = eagerEffect {
public fun <B> flatMap(f: (A) -> EagerEffect<@UnsafeVariance R, B>): EagerEffect<R, B> = eagerEffect {
i-walker marked this conversation as resolved.
Show resolved Hide resolved
f(bind()).bind()
}

public fun attempt(): EagerEffect<R, Result<A>> = eagerEffect {
kotlin.runCatching { bind() }
}

public fun handleError(f: (R) -> A): EagerEffect<Nothing, A> = eagerEffect {
public fun handleError(f: (R) -> @UnsafeVariance A): EagerEffect<Nothing, A> = eagerEffect {
fold(f, ::identity)
}

public fun <R2> handleErrorWith(f: (R) -> EagerEffect<R2, A>): EagerEffect<R2, A> =
public fun <R2> handleErrorWith(f: (R) -> EagerEffect<R2, @UnsafeVariance A>): EagerEffect<R2, A> =
eagerEffect {
toEither().fold({ r -> f(r).bind() }, ::identity)
}
Expand Down
Expand Up @@ -13,7 +13,7 @@ import kotlin.coroutines.RestrictsSuspension

/** Context of the [EagerEffect] DSL. */
@RestrictsSuspension
public interface EagerEffectScope<R> {
public interface EagerEffectScope<in R> {

/** Short-circuit the [EagerEffect] computation with value [R].
*
Expand Down
Expand Up @@ -568,7 +568,7 @@ import kotlin.coroutines.resume
* ```
* <!--- KNIT example-effect-guide-13.kt -->
*/
public interface Effect<R, A> {
public interface Effect<out R, out A> {
/**
* Runs the suspending computation by creating a [Continuation], and running the `fold` function
* over the computation.
Expand Down Expand Up @@ -637,7 +637,7 @@ public interface Effect<R, A> {
* [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<A>): Option<A> = fold(orElse, ::Some)
public suspend fun toOption(orElse: suspend (R) -> Option<@UnsafeVariance A>): Option<A> = fold(orElse, ::Some)

/**
* [fold] the [Effect] into an [A?]. Where the shifted value [R] is mapped to
Expand All @@ -654,11 +654,11 @@ public interface Effect<R, A> {
}
}

public fun handleError(recover: suspend (R) -> A): Effect<Nothing, A> = effect {
public fun handleError(recover: suspend (R) -> @UnsafeVariance A): Effect<Nothing, A> = effect {
fold(recover, ::identity)
}

public fun <R2> handleErrorWith(recover: suspend (R) -> Effect<R2, A>): Effect<R2, A> = effect {
public fun <R2> handleErrorWith(recover: suspend (R) -> Effect<R2, @UnsafeVariance A>): Effect<R2, A> = effect {
fold({ recover(it).bind() }, ::identity)
}

Expand Down
Expand Up @@ -11,7 +11,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

/** Context of the [Effect] DSL. */
public interface EffectScope<R> {
public interface EffectScope<in R> {
/**
* Short-circuit the [Effect] computation with value [R].
*
Expand Down
@@ -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<SubError>)
suspend fun subprogram(): Unit =
println("Hello SubProgram!")

context(EffectScope<OtherError>)
suspend fun otherprogram(): Unit =
println("Hello OtherProgram!")

context(EffectScope<OtherError>)
suspend fun fail(): MyResponse =
shift(OtherError.Actual)

fun main() =
runBlocking(Dispatchers.Default) {
effect<MyError, Unit> {
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<SubError>)
suspend fun respondWithBody(): BodyResponse =
BodyResponse("Hello Program!")

context(EffectScope<OtherError>)
suspend fun attemptOrError(): MyResponse =
ErrorResponse(RuntimeException("Oh no!"))

fun respond(): Effect<MyError, MyResponse> =
effect {
when (attemptOrError()) {
is BodyResponse -> respondWithBody()
EmptyResponse -> EmptyResponse
is ErrorResponse -> fail()
}
}