Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Suspend Const.fx implementation #160

Merged
merged 8 commits into from
Jun 19, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions arrow-core-data/src/main/kotlin/arrow/core/Const.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import arrow.higherkind
import arrow.typeclasses.Applicative
import arrow.typeclasses.Semigroup
import arrow.typeclasses.Show
import arrow.typeclasses.suspended.BindSyntax
import kotlin.coroutines.Continuation
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn

fun <A, T> ConstOf<A, T>.value(): A = this.fix().value()

Expand Down Expand Up @@ -39,3 +42,36 @@ fun <T, A, G> ConstOf<A, Kind<G, T>>.sequence(GA: Applicative<G>): Kind<G, Const
fix().traverse(GA, ::identity)

fun <A> A.const(): Const<A, Nothing> = Const(this)

fun <A, T> const(c: suspend EagerBind<ConstPartialOf<A>>.() -> A): Const<A, T> {
val continuation: ConstContinuation<A, A> = ConstContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Const.just(c())
} as Const<A, T>
}

suspend fun <A, T> const(c: suspend BindSyntax<ConstPartialOf<A>>.() -> A): Const<A, T> =
suspendCoroutineUninterceptedOrReturn { cont ->
val continuation = ConstSContinuation(cont as Continuation<ConstOf<A, T>>)
continuation.startCoroutineUninterceptedOrReturn {
Const.just(c())
}
}

internal class ConstSContinuation<A, T>(
parent: Continuation<ConstOf<A, T>>
) : SuspendMonadContinuation<ConstPartialOf<A>, T>(parent) {
override fun ShortCircuit.recover(): Const<A, T> =
throw this

override suspend fun <B> Kind<ConstPartialOf<A>, B>.bind(): B =
value() as B
}

internal class ConstContinuation<A, T> : MonadContinuation<ConstPartialOf<A>, T>() {
override fun ShortCircuit.recover(): Const<A, T> =
throw this

override suspend fun <B> Kind<ConstPartialOf<A>, B>.bind(): B =
value() as B
}
34 changes: 17 additions & 17 deletions arrow-core-data/src/main/kotlin/arrow/core/Either.kt
Original file line number Diff line number Diff line change
Expand Up @@ -645,13 +645,13 @@ import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
*
*
* ```kotlin:ank:playground
* import arrow.core.extensions.fx
* import arrow.core.Either
* import arrow.core.either
*
* suspend fun main() {
* val value =
* //sampleStart
* Either.fx<Int, Int> {
* either<Int, Int> {
* val (a) = Either.Right(1)
* val (b) = Either.Right(1 + a)
* val (c) = Either.Right(1 + b)
Expand Down Expand Up @@ -898,21 +898,6 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
} catch (t: Throwable) {
fe(t.nonFatalOrThrow()).left()
}

fun <E, A> fx2(c: suspend EagerBind<EitherPartialOf<E>>.() -> A): Either<E, A> {
val continuation: EitherContinuation<E, A> = EitherContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Right(c())
} as Either<E, A>
}

suspend fun <E, A> fx(c: suspend BindSyntax<EitherPartialOf<E>>.() -> A): Either<E, A> =
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
val continuation = EitherSContinuation(cont as Continuation<EitherOf<E, A>>)
continuation.startCoroutineUninterceptedOrReturn {
Right(c())
}
}
}
}

Expand Down Expand Up @@ -1114,6 +1099,21 @@ fun <A, B> EitherOf<A, B>.handleErrorWith(f: (A) -> EitherOf<A, B>): Either<A, B
}
}

fun <E, A> either(c: suspend EagerBind<EitherPartialOf<E>>.() -> A): Either<E, A> {
val continuation: EitherContinuation<E, A> = EitherContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Right(c())
} as Either<E, A>
}

suspend fun <E, A> either(c: suspend BindSyntax<EitherPartialOf<E>>.() -> A): Either<E, A> =
suspendCoroutineUninterceptedOrReturn { cont ->
val continuation = EitherSContinuation(cont as Continuation<EitherOf<E, A>>)
continuation.startCoroutineUninterceptedOrReturn {
Right(c())
}
}

internal class EitherSContinuation<E, A>(
parent: Continuation<EitherOf<E, A>>
) : SuspendMonadContinuation<EitherPartialOf<E>, A>(parent) {
Expand Down
30 changes: 15 additions & 15 deletions arrow-core-data/src/main/kotlin/arrow/core/Eval.kt
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,6 @@ sealed class Eval<out A> : EvalOf<A> {

return curr.value() as A
}

fun <A> fx2(c: suspend EagerBind<ForEval>.() -> A): Eval<A> {
val continuation: EvalContinuation<A> = EvalContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
just(c())
} as Eval<A>
}

suspend fun <A> fx(c: suspend BindSyntax<ForEval>.() -> A): Eval<A> =
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
val continuation = EvalSContinuation(cont as Continuation<EvalOf<A>>)
continuation.startCoroutineUninterceptedOrReturn {
just(c())
}
}
}

abstract fun value(): A
Expand Down Expand Up @@ -383,6 +368,21 @@ fun <A, B> Iterator<A>.iterateRight(lb: Eval<B>, f: (A, Eval<B>) -> Eval<B>): Ev
return loop()
}

fun <A> eval(c: suspend EagerBind<ForEval>.() -> A): Eval<A> {
val continuation: EvalContinuation<A> = EvalContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Eval.just(c())
} as Eval<A>
}

suspend fun <A> eval(c: suspend BindSyntax<ForEval>.() -> A): Eval<A> =
suspendCoroutineUninterceptedOrReturn { cont ->
val continuation = EvalSContinuation(cont as Continuation<EvalOf<A>>)
continuation.startCoroutineUninterceptedOrReturn {
Eval.just(c())
}
}

internal class EvalSContinuation<A>(
parent: Continuation<EvalOf<A>>
) : SuspendMonadContinuation<ForEval, A>(parent) {
Expand Down
42 changes: 23 additions & 19 deletions arrow-core-data/src/main/kotlin/arrow/core/Validated.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,13 @@ typealias Invalid<E> = Validated.Invalid<E>
* import arrow.core.Option
* import arrow.core.Some
* import arrow.core.Validated
* import arrow.core.validated
* import arrow.core.valid
* import arrow.core.invalid
*
* //sampleStart
* data class Config(val map: Map<String, String>) {
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
* val value = Validated.fromNullable(map[key]) {
* ConfigError.MissingConfig(key)
* }.bind()
Expand Down Expand Up @@ -243,6 +244,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* import arrow.core.Option
* import arrow.core.Some
* import arrow.core.Validated
* import arrow.core.validated
* import arrow.core.valid
* import arrow.core.invalid
* import arrow.core.NonEmptyList
Expand Down Expand Up @@ -275,7 +277,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* }
*
* data class Config(val map: Map<String, String>) {
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
* val value = Validated.fromNullable(map[key]) {
* ConfigError.MissingConfig(key)
* }.bind()
Expand Down Expand Up @@ -309,6 +311,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* import arrow.core.Option
* import arrow.core.Some
* import arrow.core.Validated
* import arrow.core.validated
* import arrow.core.valid
* import arrow.core.invalid
* import arrow.core.NonEmptyList
Expand Down Expand Up @@ -341,7 +344,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* }
*
* data class Config(val map: Map<String, String>) {
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
* val value = Validated.fromNullable(map[key]) {
* ConfigError.MissingConfig(key)
* }.bind()
Expand Down Expand Up @@ -382,6 +385,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* import arrow.core.right
* import arrow.core.Some
* import arrow.core.Validated
* import arrow.core.validated
* import arrow.core.valid
* import arrow.core.invalid
*
Expand All @@ -404,7 +408,7 @@ typealias Invalid<E> = Validated.Invalid<E>
* }
*
* data class Config(val map: Map<String, String>) {
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
* val value = Validated.fromNullable(map[key]) {
* ConfigError.MissingConfig(key)
* }.bind()
Expand Down Expand Up @@ -678,21 +682,6 @@ sealed class Validated<out E, out A> : ValidatedOf<E, A> {
*/
fun <E, A> fromNullable(value: A?, ifNull: () -> E): Validated<E, A> =
value?.let(::Valid) ?: Invalid(ifNull())

fun <E, A> fx2(c: suspend EagerBind<ValidatedPartialOf<E>>.() -> A): Validated<E, A> {
val continuation: ValidatedContinuation<E, A> = ValidatedContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Valid(c())
} as Validated<E, A>
}

suspend fun <E, A> fx(c: suspend BindSyntax<ValidatedPartialOf<E>>.() -> A): Validated<E, A> =
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
val continuation = ValidatedSContinuation(cont as Continuation<ValidatedOf<E, A>>)
continuation.startCoroutineUninterceptedOrReturn {
Valid(c())
}
}
}

fun show(SE: Show<E>, SA: Show<A>): String = fold({
Expand Down Expand Up @@ -906,6 +895,21 @@ fun <A> A.validNel(): ValidatedNel<Nothing, A> =
fun <E> E.invalidNel(): ValidatedNel<E, Nothing> =
Validated.invalidNel(this)

fun <E, A> validated(c: suspend EagerBind<ValidatedPartialOf<E>>.() -> A): Validated<E, A> {
val continuation: ValidatedContinuation<E, A> = ValidatedContinuation()
return continuation.startCoroutineUninterceptedAndReturn {
Valid(c())
} as Validated<E, A>
}

suspend fun <E, A> validated(c: suspend BindSyntax<ValidatedPartialOf<E>>.() -> A): Validated<E, A> =
suspendCoroutineUninterceptedOrReturn { cont ->
val continuation = ValidatedSContinuation(cont as Continuation<ValidatedOf<E, A>>)
continuation.startCoroutineUninterceptedOrReturn {
Valid(c())
}
}

internal class ValidatedSContinuation<E, A>(
parent: Continuation<ValidatedOf<E, A>>
) : SuspendMonadContinuation<ValidatedPartialOf<E>, A>(parent) {
Expand Down
22 changes: 12 additions & 10 deletions arrow-core-data/src/test/kotlin/arrow/core/ConstTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import arrow.core.test.generators.genConst
import arrow.core.test.generators.genK
import arrow.core.test.laws.ApplicativeLaws
import arrow.core.test.laws.EqLaws
import arrow.core.test.laws.FxLaws
import arrow.core.test.laws.ShowLaws
import arrow.core.test.laws.TraverseFilterLaws
import arrow.typeclasses.Eq
Expand All @@ -30,16 +31,17 @@ class ConstTest : UnitSpec() {
}

init {
Int.monoid().run {
testLaws(
TraverseFilterLaws.laws(Const.traverseFilter(),
Const.applicative(this),
Const.genK(Gen.int()),
EQK(Int.eq())),
ApplicativeLaws.laws(Const.applicative(this), Const.functor(), Const.genK(Gen.int()), EQK(Int.eq())),
EqLaws.laws(Const.eq<Int, Int>(Eq.any()), Gen.genConst<Int, Int>(Gen.int())),
ShowLaws.laws(Const.show(Int.show()), Const.eq<Int, Int>(Eq.any()), Gen.genConst<Int, Int>(Gen.int()))
val M = Int.monoid()
val EQK = EQK(Int.eq())
val GENK = Const.genK(Gen.int())
val GEN = Gen.genConst<Int, Int>(Gen.int())

testLaws(
TraverseFilterLaws.laws(Const.traverseFilter(), Const.applicative(M), GENK, EQK),
ApplicativeLaws.laws(Const.applicative(M), Const.functor(), GENK, EQK),
EqLaws.laws(Const.eq<Int, Int>(Eq.any()), GEN),
ShowLaws.laws(Const.show(Int.show()), Const.eq<Int, Int>(Eq.any()), GEN),
FxLaws.laws<ConstPartialOf<Int>, Int>(GENK.genK(Gen.int()), GENK.genK(Gen.int()), EQK.liftEq(Int.eq()), ::const, ::const)
)
}
}
}
2 changes: 1 addition & 1 deletion arrow-core-data/src/test/kotlin/arrow/core/EitherTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class EitherTest : UnitSpec() {
SemigroupKLaws.laws(Either.semigroupK(), Either.genK(Gen.id(Gen.int())), Either.eqK(Id.eq(Int.eq()))),
HashLaws.laws(Either.hash(String.hash(), Int.hash()), GEN, Either.eq(String.eq(), Int.eq())),
BicrosswalkLaws.laws(Either.bicrosswalk(), Either.genK2(), Either.eqK2()),
FxLaws.laws<EitherPartialOf<String>, Int>(Gen.int().map(::Right), GEN.map { it }, Either.eqK(String.eq()).liftEq(Int.eq()), Either.Companion::fx2, Either.Companion::fx)
FxLaws.laws<EitherPartialOf<String>, Int>(Gen.int().map(::Right), GEN.map { it }, Either.eqK(String.eq()).liftEq(Int.eq()), ::either, ::either)
)

"empty should return a Right of the empty of the inner type" {
Expand Down
2 changes: 1 addition & 1 deletion arrow-core-data/src/test/kotlin/arrow/core/EvalTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class EvalTest : UnitSpec() {

testLaws(
BimonadLaws.laws(Eval.bimonad(), Eval.monad(), Eval.comonad(), Eval.functor(), Eval.applicative(), Eval.monad(), GENK, EQK),
FxLaws.laws(G, G, EQK.liftEq(Int.eq()), Eval.Companion::fx2, Eval.Companion::fx)
FxLaws.laws<ForEval, Int>(G, G, EQK.liftEq(Int.eq()), ::eval, ::eval)
)

"should map wrapped value" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ValidatedTest : UnitSpec() {
Validated.genK2(),
Validated.eqK2()
),
FxLaws.laws<ValidatedPartialOf<String>, Int>(Gen.int().map(::Valid), Gen.validated(Gen.string(), Gen.int()).map { it }, Validated.eqK(String.eq()).liftEq(Int.eq()), Validated.Companion::fx2, Validated.Companion::fx)
FxLaws.laws<ValidatedPartialOf<String>, Int>(Gen.int().map(::Valid), Gen.validated(Gen.string(), Gen.int()).map { it }, Validated.eqK(String.eq()).liftEq(Int.eq()), ::validated, ::validated)
)

"fold should call function on Invalid" {
Expand Down
18 changes: 9 additions & 9 deletions arrow-core-test/src/main/kotlin/arrow/core/test/laws/FxLaws.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ object FxLaws {
pureGen: Gen<Kind<F, A>>, // TODO cannot specify or filter a pure generator, so we need to require an additional one
G: Gen<Kind<F, A>>,
EQ: Eq<Kind<F, A>>,
fxBlock: EagerFxBlock<F, A>,
sfxBlock: SuspendFxBlock<F, A>
fxEager: EagerFxBlock<F, A>,
fxSuspend: SuspendFxBlock<F, A>
): List<Law> = listOf(
Law("non-suspended fx can bind immediate values") { nonSuspendedCanBindImmediate(G, EQ, fxBlock) },
Law("non-suspended fx can bind immediate exceptions") { nonSuspendedCanBindImmediateException(pureGen, fxBlock) },
Law("suspended fx can bind immediate values") { suspendedCanBindImmediateValues(G, EQ, sfxBlock) },
Law("suspended fx can bind suspended values") { suspendedCanBindSuspendedValues(G, EQ, sfxBlock) },
Law("suspended fx can bind immediate exceptions") { suspendedCanBindImmediateExceptions(pureGen, sfxBlock) },
Law("suspended fx can bind suspended exceptions") { suspendedCanBindSuspendedExceptions(pureGen, sfxBlock) }
Law("non-suspended fx can bind immediate values") { nonSuspendedCanBindImmediateValues(G, EQ, fxEager) },
Law("non-suspended fx can bind immediate exceptions") { nonSuspendedCanBindImmediateException(pureGen, fxEager) },
Law("suspended fx can bind immediate values") { suspendedCanBindImmediateValues(G, EQ, fxSuspend) },
Law("suspended fx can bind suspended values") { suspendedCanBindSuspendedValues(G, EQ, fxSuspend) },
Law("suspended fx can bind immediate exceptions") { suspendedCanBindImmediateExceptions(pureGen, fxSuspend) },
Law("suspended fx can bind suspended exceptions") { suspendedCanBindSuspendedExceptions(pureGen, fxSuspend) }
)

private suspend fun <F, A> nonSuspendedCanBindImmediate(G: Gen<Kind<F, A>>, EQ: Eq<Kind<F, A>>, fxBlock: EagerFxBlock<F, A>) {
private suspend fun <F, A> nonSuspendedCanBindImmediateValues(G: Gen<Kind<F, A>>, EQ: Eq<Kind<F, A>>, fxBlock: EagerFxBlock<F, A>) {
forAll(G) { f: Kind<F, A> ->
fxBlock {
val res = !f
Expand Down