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

Transfer Cont from @nomisrev to arrow #2661

Merged
merged 114 commits into from Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 110 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
88edce8
Initial commit
nomisRev Oct 30, 2021
825f5e6
Initial impl
nomisRev Oct 30, 2021
17bc59f
Clean up, and add ior block
nomisRev Oct 30, 2021
d79382a
Reformat
nomisRev Oct 30, 2021
5678c42
Add Cont#toEither, and optimise OptionEffect
nomisRev Oct 30, 2021
d1b0985
Add some docs
nomisRev Oct 30, 2021
9d6448b
Concurrency fix Ior
nomisRev Oct 30, 2021
60ce519
Add monadic syntax
nomisRev Oct 31, 2021
404b7d5
Fix bug shift
nomisRev Nov 1, 2021
97cb176
Add attempt
nomisRev Nov 2, 2021
4308475
Add restricted cont implementation
nomisRev Nov 6, 2021
2d3ed21
Run workflow on push main
nomisRev Nov 6, 2021
89657b8
Fix workflow
nomisRev Nov 6, 2021
b05bde8
Fix try/catch over shift
nomisRev Nov 6, 2021
e4b6218
Add docs
nomisRev Nov 6, 2021
d0dbf79
Use ControlThrowable, and add more tests Structured Concurrency
nomisRev Nov 7, 2021
116632e
Add documentation
nomisRev Nov 7, 2021
31a878d
Optimise how shift/fold run, and allow catching shift
nomisRev Nov 7, 2021
27a0a36
Revert catch
nomisRev Nov 7, 2021
572ac3c
Add deeply nested exception tests
nomisRev Nov 7, 2021
fd97ee1
Add weird scenarios
nomisRev Nov 7, 2021
340a104
Add some docs structurred concurrency
nomisRev Nov 8, 2021
5773eb2
Try KotlinX Knit
nomisRev Nov 9, 2021
1134615
Add more Knit examples, and setup Dokka
nomisRev Nov 9, 2021
edadce4
Setup docs
nomisRev Nov 9, 2021
a2e4cfc
Rename branch
nomisRev Nov 9, 2021
65c325e
Auto update docs
nomisRev Nov 9, 2021
15c8a53
Update knit
nomisRev Nov 9, 2021
9cb1b71
Auto update docs
nomisRev Nov 9, 2021
b8abf3b
Update build to MPP
nomisRev Nov 9, 2021
b26645c
Move test to common
nomisRev Nov 9, 2021
0258182
Auto update docs
nomisRev Nov 9, 2021
19ab95a
Reemove upload test reports
nomisRev Nov 9, 2021
dda5dcc
Try Kotest M2
nomisRev Nov 9, 2021
ef7978f
Use Kotest knit template, and use Kotest assertion inside examples
nomisRev Nov 10, 2021
e340f55
Auto update docs
nomisRev Nov 10, 2021
a11ae4c
Property based doc testing
nomisRev Nov 10, 2021
98a6fb3
toResult compiler bug
nomisRev Nov 10, 2021
96f964b
More docs
nomisRev Nov 10, 2021
3311bc9
Auto update docs
nomisRev Nov 10, 2021
33ed1b0
More docs
nomisRev Nov 11, 2021
de30d73
Update to Kotlin 1.6.0-RC2
nomisRev Nov 12, 2021
c705610
Fix github workflow
nomisRev Nov 12, 2021
3075003
Auto update docs
nomisRev Nov 12, 2021
4a34dc3
jvmTest only
nomisRev Nov 12, 2021
6350e74
Try again
nomisRev Nov 12, 2021
86db144
Bump Knit & Kotlin
nomisRev Nov 15, 2021
ce90387
Disable MPP test due to bug Kotlin 1.6.0 annotations
nomisRev Nov 22, 2021
e7613c5
Update renovate
nomisRev Nov 22, 2021
65eddd1
Update all dependencies
renovate-bot Nov 22, 2021
cf3ecc2
Update actions/cache action to v2.1.7
renovate-bot Nov 23, 2021
698eacc
Update all dependencies to v1.6.0
renovate-bot Nov 23, 2021
31692c5
Auto update docs
renovate[bot] Nov 23, 2021
4f4d48f
Update all dependencies to v5.0.0.RC2
renovate-bot Nov 24, 2021
1880f98
test all targets with Kotest RC2
nomisRev Nov 24, 2021
8b935e7
Change check to test due to Spotless not working
nomisRev Nov 24, 2021
f61681d
exclude spotless
nomisRev Nov 24, 2021
b4b1fd1
Fix report upload
nomisRev Nov 25, 2021
43e1674
Add kover
nomisRev Nov 29, 2021
743e10f
Update plugin kover to v0.4.4
renovate-bot Nov 30, 2021
972ce93
Update all dependencies to v5.0.1
renovate-bot Nov 30, 2021
bc0030d
Update plugin kotest-multiplatform to v5.0.1
renovate-bot Dec 1, 2021
c4c218c
Update actions/upload-artifact action to v2.3.0
renovate-bot Dec 7, 2021
4eb29ba
Update all dependencies to v0.6.0
renovate-bot Dec 8, 2021
f5a6422
Fix mistake in README
nomisRev Dec 10, 2021
632f784
Update all dependencies
renovate-bot Dec 22, 2021
73f5845
redefine Cont to Effect and ContEffect to EffectScope, reorg and rm a…
i-walker Feb 3, 2022
77bbf0e
clean up workflow files and add knit files
i-walker Feb 3, 2022
9a707ac
rm guide files
i-walker Feb 3, 2022
754e4c5
rm knit props
i-walker Feb 3, 2022
83416c4
typo
i-walker Feb 3, 2022
d5160f7
rm option.eager
i-walker Feb 3, 2022
e0e4668
rm computation imports
i-walker Feb 3, 2022
061f440
reorg
i-walker Feb 3, 2022
c9327ed
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
cff1103
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
3474894
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
353ce05
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
7d12594
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
f3b1b8c
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 5, 2022
a2424d6
Update API files
i-walker Feb 5, 2022
b29c92b
add docs to website
i-walker Feb 6, 2022
6c1a69e
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker Feb 6, 2022
c74196d
add missing functions and option Effect scope
i-walker Feb 7, 2022
5ed333e
change OptionEffectscope to be a class
i-walker Feb 7, 2022
ddd1c6b
Update API files
i-walker Feb 7, 2022
fff0ef9
add Eager implementations and EagerEffectSpec
i-walker Feb 7, 2022
609aa43
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker Feb 7, 2022
812b78b
run eager tests
i-walker Feb 7, 2022
3841a95
fix eager spec tests
i-walker Feb 7, 2022
cf52350
clean up
i-walker Feb 7, 2022
a7b35c0
use contract system
i-walker Feb 8, 2022
75dc29c
Update API files
i-walker Feb 8, 2022
197a49a
clean up and add apiDump
i-walker Feb 8, 2022
e954781
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker Feb 8, 2022
f062352
add eager option tests
i-walker Feb 8, 2022
76b26f6
add similar tests from arrow.comptutations.nullable
i-walker Feb 8, 2022
5f4f135
clean up dead test code
i-walker Feb 8, 2022
c1c185b
fix typo
i-walker Feb 8, 2022
a3a9a65
fix nullable tests
i-walker Feb 8, 2022
3e4b2d6
add docs for all types in arrow.core.continuations based on Cont and …
i-walker Feb 10, 2022
81b069f
add it to core
i-walker Feb 10, 2022
1f54966
Merge remote-tracking branch 'origin/main' into is-effect
i-walker Feb 11, 2022
74257e5
adjust for low-level use-cases, making shiftcancellations distinguish…
i-walker Feb 16, 2022
bb1f465
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker Feb 16, 2022
8e60a66
Update API files
i-walker Feb 16, 2022
9405fb8
add links and fix docs
i-walker Feb 16, 2022
9112cdf
remove outdated transactionEither example
i-walker Feb 16, 2022
a1364c3
effect interoperates with eagerEffect
i-walker Feb 16, 2022
d4b8baf
rm toEffect
i-walker Feb 16, 2022
16212c2
Merge branch 'main' into is-effect
nomisRev Feb 21, 2022
cbb2707
Update arrow-site/docs/_data/sidebar-fx.yml
i-walker Feb 21, 2022
c90ff7e
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 21, 2022
9479e77
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker Feb 21, 2022
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
268 changes: 268 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions arrow-libs/core/arrow-core/build.gradle.kts
Expand Up @@ -20,6 +20,7 @@ kotlin {
commonTest {
dependencies {
implementation(projects.arrowCoreTest)
implementation(projects.arrowFxCoroutines)
}
}
jvmMain {
Expand Down
Expand Up @@ -34,7 +34,7 @@ public fun interface OptionEffect<A> : Effect<Option<A>> {
* // println: "ensure(true) passes"
* // res: None
* ```
* <!--- KNIT example-option-computations-01.kt -->
* <!--- KNIT example-option-computations-01.kt -->
*/
public suspend fun ensure(value: Boolean): Unit =
if (value) Unit else control().shift(None)
Expand Down Expand Up @@ -73,6 +73,7 @@ public suspend fun <B : Any> OptionEffect<*>.ensureNotNull(value: B?): B {

return value ?: (this as OptionEffect<Any?>).control().shift(None)
}

@RestrictsSuspension
public fun interface RestrictedOptionEffect<A> : OptionEffect<A>

Expand Down
@@ -0,0 +1,197 @@
package arrow.core.continuations

import arrow.core.Either
import arrow.core.Ior
import arrow.core.Option
import arrow.core.Some
import arrow.core.Validated
import arrow.core.identity
import arrow.core.nonFatalOrThrow
import kotlin.coroutines.Continuation
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
import kotlin.coroutines.RestrictsSuspension

/**
* [RestrictsSuspension] version of [Effect]. This version runs eagerly, and can be used in
* non-suspending code.
* An [effect] computation interoperates with an [EagerEffect] via `bind`.
* @see Effect
*/
public interface EagerEffect<R, A> {

/**
* Runs the non-suspending computation by creating a [Continuation] with an [EmptyCoroutineContext],
* and running the `fold` function over the computation.
*
* When the [EagerEffect] has shifted with [R] it will [recover] the shifted value to [B], and when it
* ran the computation to completion it will [transform] the value [A] to [B].
*
* ```kotlin
* import arrow.core.continuations.eagerEffect
* import io.kotest.matchers.shouldBe
*
* fun main() {
* val shift = eagerEffect<String, Int> {
* shift("Hello, World!")
* }.fold({ str: String -> str }, { int -> int.toString() })
* shift shouldBe "Hello, World!"
*
* val res = eagerEffect<String, Int> {
* 1000
* }.fold({ str: String -> str.length }, { int -> int })
* res shouldBe 1000
* }
* ```
* <!--- KNIT example-eager-effect-01.kt -->
*/
public fun <B> fold(recover: (R) -> B, transform: (A) -> B): B

/**
* Like `fold` but also allows folding over any unexpected [Throwable] that might have occurred.
* @see fold
*/
public fun <B> fold(
error: (error: Throwable) -> B,
recover: (shifted: R) -> B,
transform: (value: A) -> B
): B =
try {
fold(recover, transform)
} catch (e: Throwable) {
error(e.nonFatalOrThrow())
}

/**
* [fold] the [EagerEffect] into an [Ior]. Where the shifted value [R] is mapped to [Ior.Left], and
* result value [A] is mapped to [Ior.Right].
*/
public fun toIor(): Ior<R, A> = fold({ Ior.Left(it) }) { Ior.Right(it) }

/**
* [fold] the [EagerEffect] into an [Either]. Where the shifted value [R] is mapped to [Either.Left], and
* result value [A] is mapped to [Either.Right].
*/
public fun toEither(): Either<R, A> = fold({ Either.Left(it) }) { Either.Right(it) }

/**
* [fold] the [EagerEffect] into an [Validated]. Where the shifted value [R] is mapped to
* [Validated.Invalid], and result value [A] is mapped to [Validated.Valid].
*/
public fun toValidated(): Validated<R, A> =
fold({ Validated.Invalid(it) }) { Validated.Valid(it) }

/**
* [fold] the [EagerEffect] into an [A?]. Where the shifted value [R] is mapped to
* [null], and result value [A].
*/
public fun orNull(): A? = fold({ null }, ::identity)

/**
* [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> =
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 {
f(bind()).bind()
}

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

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

public fun <R2> handleErrorWith(f: (R) -> EagerEffect<R2, A>): EagerEffect<R2, A> =
eagerEffect {
toEither().fold({ r -> f(r).bind() }, ::identity)
}

public fun <B> redeem(f: (R) -> B, g: (A) -> B): EagerEffect<Nothing, B> = eagerEffect {
fold(f, g)
}

public fun <R2, B> redeemWith(
f: (R) -> EagerEffect<R2, B>,
g: (A) -> EagerEffect<R2, B>
): EagerEffect<R2, B> = eagerEffect { fold(f, g).bind() }
}

@PublishedApi
internal class Eager(val token: Token, val shifted: Any?, val recover: (Any?) -> Any?) :
ShiftCancellationException() {
override fun toString(): String = "ShiftCancellationException($message)"
}
Comment on lines +127 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use the same trick here as we do for Effect, so users have an escape hatch to differentiate between JobCanncellationException and ShiftCanncelationException when they have to in low-level niche use-cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added tests in EagerEffect and Effect for low-level use-cases


/**
* DSL for constructing `EagerEffect<R, A>` values
*
* ```kotlin
* import arrow.core.Either
* import arrow.core.None
* import arrow.core.Option
* import arrow.core.Validated
* import arrow.core.continuations.eagerEffect
* import io.kotest.assertions.fail
* import io.kotest.matchers.shouldBe
*
* fun main() {
* eagerEffect<String, Int> {
* val x = Either.Right(1).bind()
* val y = Validated.Valid(2).bind()
* val z = Option(3).bind { "Option was empty" }
* x + y + z
* }.fold({ fail("Shift can never be the result") }, { it shouldBe 6 })
*
* eagerEffect<String, Int> {
* val x = Either.Right(1).bind()
* val y = Validated.Valid(2).bind()
* val z: Int = None.bind { "Option was empty" }
* x + y + z
* }.fold({ it shouldBe "Option was empty" }, { fail("Int can never be the result") })
* }
* ```
* <!--- KNIT example-eager-effect-02.kt -->
*/
public inline fun <R, A> eagerEffect(crossinline f: suspend EagerEffectScope<R>.() -> A): EagerEffect<R, A> =
object : EagerEffect<R, A> {
override fun <B> fold(recover: (R) -> B, transform: (A) -> B): B {
val token = Token()
val eagerEffectScope =
object : EagerEffectScope<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: ContSpec - try/catch tests
i-walker marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
}