Skip to content

Instantly share code, notes, and snippets.

@nomisRev
Created October 29, 2021 16:10
Show Gist options
  • Save nomisRev/d12beb058acc4db5f86995024836b1c5 to your computer and use it in GitHub Desktop.
Save nomisRev/d12beb058acc4db5f86995024836b1c5 to your computer and use it in GitHub Desktop.
Cont<R, A> implementation in Kotlin
import arrow.core.identity
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
interface ContEffect<R, A> {
suspend fun <B> shift(r: R): B
suspend fun <B> Cont<R, B>.bind(): B =
fold(this@ContEffect::shift, ::identity)
}
/**
* A Continuation of type [R] and [A] will
* - "Successfully" complete with a value of [A].
* - Short-circuit with a value of [R].
*/
interface Cont<R, A> {
suspend fun <B> fold(f: suspend (R) -> B, g: suspend (A) -> B): B
}
fun <R, A> cont(f: suspend ContEffect<R, A>.() -> A): Cont<R, A> =
object : Cont<R, A> {
override suspend fun <B> fold(f: suspend (R) -> B, g: suspend (A) -> B): B =
suspendCoroutineUninterceptedOrReturn { cont ->
val effect = object : ContEffect<R, A> {
// Shift away from this Continuation by intercepting it, and completing it.
// The Continuation we grab here, is the same one from line 25 which is constructed on line 40
// So this is a single continuation, completing it with it's final value means the remainder of the
// continuation program is short-circuited
override suspend fun <B> shift(r: R): B {
val b = f(r)
return suspendCoroutineUninterceptedOrReturn { _ ->
cont.resume(b)
COROUTINE_SUSPENDED
}
}
}
suspend { g(f(effect)) }
.startCoroutineUninterceptedOrReturn(Continuation(cont.context, cont::resumeWith))
}
}
import arrow.core.Either
import arrow.core.identity
interface EitherEffect<E> : ContEffect<Either.Left<E>, Any?> {
suspend fun <A> Either<E, A>.bind(): A =
when (this) {
is Either.Left -> shift(this)
is Either.Right -> value
}
}
suspend fun <E, A> either(f: suspend EitherEffect<E>.() -> A): Either<E, A> =
cont<Either.Left<E>, A> {
val effect = EitherEffect(this@cont)
f(effect)
}.fold(::identity) { Either.Right(it) }
@Suppress("UNCHECKED_CAST")
fun <E, A> EitherEffect(cont: ContEffect<Either.Left<E>, A>): EitherEffect<E> =
object : EitherEffect<E>,
ContSyntax<Either.Left<E>, Any?> by cont as ContEffect<Either.Left<E>, Any?> {
}
import arrow.core.Either
import arrow.core.identity
suspend fun main() {
// A Cont<R, A> that either results in `Int` or shifts (short-circuits) with String.
val cont: Cont<String, Int> = cont {
val x: Long = shift("Hello World!")
10
}
// Run Cont<String, Int> to String by mapping the `Int` case to `String`.
val x: String = cont.fold(::identity, Int::toString)
println(x)
// Either Monad program inside Cont
val eitherCont = cont<Either<String, Nothing>, Int> {
val double: Double = shift(Either.Left("test"))
1
}
// Converge Cont<Either<String, Nothing>, Int> to Either<String, Int>
val res: Either<String, Int> = eitherCont.fold(::identity) { Either.Right(it) }
println(res)
// Consume both values of `Cont<Either<String, Nothing>` into `Unit`
val unit = eitherCont.fold(::println, ::println)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment