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
Changes from 110 commits
Commits
Show all changes
114 commits
Select commit
Hold shift + click to select a range
88edce8
Initial commit
nomisRev 825f5e6
Initial impl
nomisRev 17bc59f
Clean up, and add ior block
nomisRev d79382a
Reformat
nomisRev 5678c42
Add Cont#toEither, and optimise OptionEffect
nomisRev d1b0985
Add some docs
nomisRev 9d6448b
Concurrency fix Ior
nomisRev 60ce519
Add monadic syntax
nomisRev 404b7d5
Fix bug shift
nomisRev 97cb176
Add attempt
nomisRev 4308475
Add restricted cont implementation
nomisRev 2d3ed21
Run workflow on push main
nomisRev 89657b8
Fix workflow
nomisRev b05bde8
Fix try/catch over shift
nomisRev e4b6218
Add docs
nomisRev d0dbf79
Use ControlThrowable, and add more tests Structured Concurrency
nomisRev 116632e
Add documentation
nomisRev 31a878d
Optimise how shift/fold run, and allow catching shift
nomisRev 27a0a36
Revert catch
nomisRev 572ac3c
Add deeply nested exception tests
nomisRev fd97ee1
Add weird scenarios
nomisRev 340a104
Add some docs structurred concurrency
nomisRev 5773eb2
Try KotlinX Knit
nomisRev 1134615
Add more Knit examples, and setup Dokka
nomisRev edadce4
Setup docs
nomisRev a2e4cfc
Rename branch
nomisRev 65c325e
Auto update docs
nomisRev 15c8a53
Update knit
nomisRev 9cb1b71
Auto update docs
nomisRev b8abf3b
Update build to MPP
nomisRev b26645c
Move test to common
nomisRev 0258182
Auto update docs
nomisRev 19ab95a
Reemove upload test reports
nomisRev dda5dcc
Try Kotest M2
nomisRev ef7978f
Use Kotest knit template, and use Kotest assertion inside examples
nomisRev e340f55
Auto update docs
nomisRev a11ae4c
Property based doc testing
nomisRev 98a6fb3
toResult compiler bug
nomisRev 96f964b
More docs
nomisRev 3311bc9
Auto update docs
nomisRev 33ed1b0
More docs
nomisRev de30d73
Update to Kotlin 1.6.0-RC2
nomisRev c705610
Fix github workflow
nomisRev 3075003
Auto update docs
nomisRev 4a34dc3
jvmTest only
nomisRev 6350e74
Try again
nomisRev 86db144
Bump Knit & Kotlin
nomisRev ce90387
Disable MPP test due to bug Kotlin 1.6.0 annotations
nomisRev e7613c5
Update renovate
nomisRev 65eddd1
Update all dependencies
renovate-bot cf3ecc2
Update actions/cache action to v2.1.7
renovate-bot 698eacc
Update all dependencies to v1.6.0
renovate-bot 31692c5
Auto update docs
renovate[bot] 4f4d48f
Update all dependencies to v5.0.0.RC2
renovate-bot 1880f98
test all targets with Kotest RC2
nomisRev 8b935e7
Change check to test due to Spotless not working
nomisRev f61681d
exclude spotless
nomisRev b4b1fd1
Fix report upload
nomisRev 43e1674
Add kover
nomisRev 743e10f
Update plugin kover to v0.4.4
renovate-bot 972ce93
Update all dependencies to v5.0.1
renovate-bot bc0030d
Update plugin kotest-multiplatform to v5.0.1
renovate-bot c4c218c
Update actions/upload-artifact action to v2.3.0
renovate-bot 4eb29ba
Update all dependencies to v0.6.0
renovate-bot f5a6422
Fix mistake in README
nomisRev 632f784
Update all dependencies
renovate-bot 73f5845
redefine Cont to Effect and ContEffect to EffectScope, reorg and rm a…
i-walker 77bbf0e
clean up workflow files and add knit files
i-walker 9a707ac
rm guide files
i-walker 754e4c5
rm knit props
i-walker 83416c4
typo
i-walker d5160f7
rm option.eager
i-walker e0e4668
rm computation imports
i-walker 061f440
reorg
i-walker c9327ed
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker cff1103
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker 3474894
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker 353ce05
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker 7d12594
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker f3b1b8c
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker a2424d6
Update API files
i-walker b29c92b
add docs to website
i-walker 6c1a69e
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker c74196d
add missing functions and option Effect scope
i-walker 5ed333e
change OptionEffectscope to be a class
i-walker ddd1c6b
Update API files
i-walker fff0ef9
add Eager implementations and EagerEffectSpec
i-walker 609aa43
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker 812b78b
run eager tests
i-walker 3841a95
fix eager spec tests
i-walker cf52350
clean up
i-walker a7b35c0
use contract system
i-walker 75dc29c
Update API files
i-walker 197a49a
clean up and add apiDump
i-walker e954781
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker f062352
add eager option tests
i-walker 76b26f6
add similar tests from arrow.comptutations.nullable
i-walker 5f4f135
clean up dead test code
i-walker c1c185b
fix typo
i-walker a3a9a65
fix nullable tests
i-walker 3e4b2d6
add docs for all types in arrow.core.continuations based on Cont and …
i-walker 81b069f
add it to core
i-walker 1f54966
Merge remote-tracking branch 'origin/main' into is-effect
i-walker 74257e5
adjust for low-level use-cases, making shiftcancellations distinguish…
i-walker bb1f465
Merge remote-tracking branch 'origin/is-effect' into is-effect
i-walker 8e60a66
Update API files
i-walker 9405fb8
add links and fix docs
i-walker 9112cdf
remove outdated transactionEither example
i-walker a1364c3
effect interoperates with eagerEffect
i-walker d4b8baf
rm toEffect
i-walker 16212c2
Merge branch 'main' into is-effect
nomisRev cbb2707
Update arrow-site/docs/_data/sidebar-fx.yml
i-walker c90ff7e
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker 9479e77
Update arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/co…
i-walker File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/continuations/EagerEffect.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)" | ||
} | ||
|
||
/** | ||
* 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 | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 betweenJobCanncellationException
andShiftCanncelationException
when they have to in low-level niche use-cases.There was a problem hiding this comment.
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