Skip to content

Commit 1cbbc81

Browse files
authored
Suspend Const.fx implementation (#160)
* Add fx blocks to Const * convert all fx blocks to top-level functions and rename to match the datatype
1 parent 4129b5f commit 1cbbc81

File tree

9 files changed

+115
-73
lines changed

9 files changed

+115
-73
lines changed

arrow-libs/core/arrow-core-data/src/main/kotlin/arrow/core/Const.kt

+36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import arrow.higherkind
55
import arrow.typeclasses.Applicative
66
import arrow.typeclasses.Semigroup
77
import arrow.typeclasses.Show
8+
import arrow.typeclasses.suspended.BindSyntax
9+
import kotlin.coroutines.Continuation
10+
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
811

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

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

4144
fun <A> A.const(): Const<A, Nothing> = Const(this)
45+
46+
fun <A, T> const(c: suspend EagerBind<ConstPartialOf<A>>.() -> A): Const<A, T> {
47+
val continuation: ConstContinuation<A, A> = ConstContinuation()
48+
return continuation.startCoroutineUninterceptedAndReturn {
49+
Const.just(c())
50+
} as Const<A, T>
51+
}
52+
53+
suspend fun <A, T> const(c: suspend BindSyntax<ConstPartialOf<A>>.() -> A): Const<A, T> =
54+
suspendCoroutineUninterceptedOrReturn { cont ->
55+
val continuation = ConstSContinuation(cont as Continuation<ConstOf<A, T>>)
56+
continuation.startCoroutineUninterceptedOrReturn {
57+
Const.just(c())
58+
}
59+
}
60+
61+
internal class ConstSContinuation<A, T>(
62+
parent: Continuation<ConstOf<A, T>>
63+
) : SuspendMonadContinuation<ConstPartialOf<A>, T>(parent) {
64+
override fun ShortCircuit.recover(): Const<A, T> =
65+
throw this
66+
67+
override suspend fun <B> Kind<ConstPartialOf<A>, B>.bind(): B =
68+
value() as B
69+
}
70+
71+
internal class ConstContinuation<A, T> : MonadContinuation<ConstPartialOf<A>, T>() {
72+
override fun ShortCircuit.recover(): Const<A, T> =
73+
throw this
74+
75+
override suspend fun <B> Kind<ConstPartialOf<A>, B>.bind(): B =
76+
value() as B
77+
}

arrow-libs/core/arrow-core-data/src/main/kotlin/arrow/core/Either.kt

+17-17
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,13 @@ import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
645645
*
646646
*
647647
* ```kotlin:ank:playground
648-
* import arrow.core.extensions.fx
649648
* import arrow.core.Either
649+
* import arrow.core.either
650650
*
651651
* suspend fun main() {
652652
* val value =
653653
* //sampleStart
654-
* Either.fx<Int, Int> {
654+
* either<Int, Int> {
655655
* val (a) = Either.Right(1)
656656
* val (b) = Either.Right(1 + a)
657657
* val (c) = Either.Right(1 + b)
@@ -898,21 +898,6 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
898898
} catch (t: Throwable) {
899899
fe(t.nonFatalOrThrow()).left()
900900
}
901-
902-
fun <E, A> fx2(c: suspend EagerBind<EitherPartialOf<E>>.() -> A): Either<E, A> {
903-
val continuation: EitherContinuation<E, A> = EitherContinuation()
904-
return continuation.startCoroutineUninterceptedAndReturn {
905-
Right(c())
906-
} as Either<E, A>
907-
}
908-
909-
suspend fun <E, A> fx(c: suspend BindSyntax<EitherPartialOf<E>>.() -> A): Either<E, A> =
910-
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
911-
val continuation = EitherSContinuation(cont as Continuation<EitherOf<E, A>>)
912-
continuation.startCoroutineUninterceptedOrReturn {
913-
Right(c())
914-
}
915-
}
916901
}
917902
}
918903

@@ -1114,6 +1099,21 @@ fun <A, B> EitherOf<A, B>.handleErrorWith(f: (A) -> EitherOf<A, B>): Either<A, B
11141099
}
11151100
}
11161101

1102+
fun <E, A> either(c: suspend EagerBind<EitherPartialOf<E>>.() -> A): Either<E, A> {
1103+
val continuation: EitherContinuation<E, A> = EitherContinuation()
1104+
return continuation.startCoroutineUninterceptedAndReturn {
1105+
Right(c())
1106+
} as Either<E, A>
1107+
}
1108+
1109+
suspend fun <E, A> either(c: suspend BindSyntax<EitherPartialOf<E>>.() -> A): Either<E, A> =
1110+
suspendCoroutineUninterceptedOrReturn { cont ->
1111+
val continuation = EitherSContinuation(cont as Continuation<EitherOf<E, A>>)
1112+
continuation.startCoroutineUninterceptedOrReturn {
1113+
Right(c())
1114+
}
1115+
}
1116+
11171117
internal class EitherSContinuation<E, A>(
11181118
parent: Continuation<EitherOf<E, A>>
11191119
) : SuspendMonadContinuation<EitherPartialOf<E>, A>(parent) {

arrow-libs/core/arrow-core-data/src/main/kotlin/arrow/core/Eval.kt

+15-15
Original file line numberDiff line numberDiff line change
@@ -242,21 +242,6 @@ sealed class Eval<out A> : EvalOf<A> {
242242

243243
return curr.value() as A
244244
}
245-
246-
fun <A> fx2(c: suspend EagerBind<ForEval>.() -> A): Eval<A> {
247-
val continuation: EvalContinuation<A> = EvalContinuation()
248-
return continuation.startCoroutineUninterceptedAndReturn {
249-
just(c())
250-
} as Eval<A>
251-
}
252-
253-
suspend fun <A> fx(c: suspend BindSyntax<ForEval>.() -> A): Eval<A> =
254-
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
255-
val continuation = EvalSContinuation(cont as Continuation<EvalOf<A>>)
256-
continuation.startCoroutineUninterceptedOrReturn {
257-
just(c())
258-
}
259-
}
260245
}
261246

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

371+
fun <A> eval(c: suspend EagerBind<ForEval>.() -> A): Eval<A> {
372+
val continuation: EvalContinuation<A> = EvalContinuation()
373+
return continuation.startCoroutineUninterceptedAndReturn {
374+
Eval.just(c())
375+
} as Eval<A>
376+
}
377+
378+
suspend fun <A> eval(c: suspend BindSyntax<ForEval>.() -> A): Eval<A> =
379+
suspendCoroutineUninterceptedOrReturn { cont ->
380+
val continuation = EvalSContinuation(cont as Continuation<EvalOf<A>>)
381+
continuation.startCoroutineUninterceptedOrReturn {
382+
Eval.just(c())
383+
}
384+
}
385+
386386
internal class EvalSContinuation<A>(
387387
parent: Continuation<EvalOf<A>>
388388
) : SuspendMonadContinuation<ForEval, A>(parent) {

arrow-libs/core/arrow-core-data/src/main/kotlin/arrow/core/Validated.kt

+23-19
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,13 @@ typealias Invalid<E> = Validated.Invalid<E>
142142
* import arrow.core.Option
143143
* import arrow.core.Some
144144
* import arrow.core.Validated
145+
* import arrow.core.validated
145146
* import arrow.core.valid
146147
* import arrow.core.invalid
147148
*
148149
* //sampleStart
149150
* data class Config(val map: Map<String, String>) {
150-
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
151+
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
151152
* val value = Validated.fromNullable(map[key]) {
152153
* ConfigError.MissingConfig(key)
153154
* }.bind()
@@ -243,6 +244,7 @@ typealias Invalid<E> = Validated.Invalid<E>
243244
* import arrow.core.Option
244245
* import arrow.core.Some
245246
* import arrow.core.Validated
247+
* import arrow.core.validated
246248
* import arrow.core.valid
247249
* import arrow.core.invalid
248250
* import arrow.core.NonEmptyList
@@ -275,7 +277,7 @@ typealias Invalid<E> = Validated.Invalid<E>
275277
* }
276278
*
277279
* data class Config(val map: Map<String, String>) {
278-
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
280+
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
279281
* val value = Validated.fromNullable(map[key]) {
280282
* ConfigError.MissingConfig(key)
281283
* }.bind()
@@ -309,6 +311,7 @@ typealias Invalid<E> = Validated.Invalid<E>
309311
* import arrow.core.Option
310312
* import arrow.core.Some
311313
* import arrow.core.Validated
314+
* import arrow.core.validated
312315
* import arrow.core.valid
313316
* import arrow.core.invalid
314317
* import arrow.core.NonEmptyList
@@ -341,7 +344,7 @@ typealias Invalid<E> = Validated.Invalid<E>
341344
* }
342345
*
343346
* data class Config(val map: Map<String, String>) {
344-
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
347+
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
345348
* val value = Validated.fromNullable(map[key]) {
346349
* ConfigError.MissingConfig(key)
347350
* }.bind()
@@ -382,6 +385,7 @@ typealias Invalid<E> = Validated.Invalid<E>
382385
* import arrow.core.right
383386
* import arrow.core.Some
384387
* import arrow.core.Validated
388+
* import arrow.core.validated
385389
* import arrow.core.valid
386390
* import arrow.core.invalid
387391
*
@@ -404,7 +408,7 @@ typealias Invalid<E> = Validated.Invalid<E>
404408
* }
405409
*
406410
* data class Config(val map: Map<String, String>) {
407-
* suspend fun <A> parse(read: Read<A>, key: String) = Validated.fx<ConfigError, A> {
411+
* suspend fun <A> parse(read: Read<A>, key: String) = validated<ConfigError, A> {
408412
* val value = Validated.fromNullable(map[key]) {
409413
* ConfigError.MissingConfig(key)
410414
* }.bind()
@@ -678,21 +682,6 @@ sealed class Validated<out E, out A> : ValidatedOf<E, A> {
678682
*/
679683
fun <E, A> fromNullable(value: A?, ifNull: () -> E): Validated<E, A> =
680684
value?.let(::Valid) ?: Invalid(ifNull())
681-
682-
fun <E, A> fx2(c: suspend EagerBind<ValidatedPartialOf<E>>.() -> A): Validated<E, A> {
683-
val continuation: ValidatedContinuation<E, A> = ValidatedContinuation()
684-
return continuation.startCoroutineUninterceptedAndReturn {
685-
Valid(c())
686-
} as Validated<E, A>
687-
}
688-
689-
suspend fun <E, A> fx(c: suspend BindSyntax<ValidatedPartialOf<E>>.() -> A): Validated<E, A> =
690-
suspendCoroutineUninterceptedOrReturn sc@{ cont ->
691-
val continuation = ValidatedSContinuation(cont as Continuation<ValidatedOf<E, A>>)
692-
continuation.startCoroutineUninterceptedOrReturn {
693-
Valid(c())
694-
}
695-
}
696685
}
697686

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

898+
fun <E, A> validated(c: suspend EagerBind<ValidatedPartialOf<E>>.() -> A): Validated<E, A> {
899+
val continuation: ValidatedContinuation<E, A> = ValidatedContinuation()
900+
return continuation.startCoroutineUninterceptedAndReturn {
901+
Valid(c())
902+
} as Validated<E, A>
903+
}
904+
905+
suspend fun <E, A> validated(c: suspend BindSyntax<ValidatedPartialOf<E>>.() -> A): Validated<E, A> =
906+
suspendCoroutineUninterceptedOrReturn { cont ->
907+
val continuation = ValidatedSContinuation(cont as Continuation<ValidatedOf<E, A>>)
908+
continuation.startCoroutineUninterceptedOrReturn {
909+
Valid(c())
910+
}
911+
}
912+
909913
internal class ValidatedSContinuation<E, A>(
910914
parent: Continuation<ValidatedOf<E, A>>
911915
) : SuspendMonadContinuation<ValidatedPartialOf<E>, A>(parent) {

arrow-libs/core/arrow-core-data/src/test/kotlin/arrow/core/ConstTest.kt

+12-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import arrow.core.test.generators.genConst
1414
import arrow.core.test.generators.genK
1515
import arrow.core.test.laws.ApplicativeLaws
1616
import arrow.core.test.laws.EqLaws
17+
import arrow.core.test.laws.FxLaws
1718
import arrow.core.test.laws.ShowLaws
1819
import arrow.core.test.laws.TraverseFilterLaws
1920
import arrow.typeclasses.Eq
@@ -30,16 +31,17 @@ class ConstTest : UnitSpec() {
3031
}
3132

3233
init {
33-
Int.monoid().run {
34-
testLaws(
35-
TraverseFilterLaws.laws(Const.traverseFilter(),
36-
Const.applicative(this),
37-
Const.genK(Gen.int()),
38-
EQK(Int.eq())),
39-
ApplicativeLaws.laws(Const.applicative(this), Const.functor(), Const.genK(Gen.int()), EQK(Int.eq())),
40-
EqLaws.laws(Const.eq<Int, Int>(Eq.any()), Gen.genConst<Int, Int>(Gen.int())),
41-
ShowLaws.laws(Const.show(Int.show()), Const.eq<Int, Int>(Eq.any()), Gen.genConst<Int, Int>(Gen.int()))
34+
val M = Int.monoid()
35+
val EQK = EQK(Int.eq())
36+
val GENK = Const.genK(Gen.int())
37+
val GEN = Gen.genConst<Int, Int>(Gen.int())
38+
39+
testLaws(
40+
TraverseFilterLaws.laws(Const.traverseFilter(), Const.applicative(M), GENK, EQK),
41+
ApplicativeLaws.laws(Const.applicative(M), Const.functor(), GENK, EQK),
42+
EqLaws.laws(Const.eq<Int, Int>(Eq.any()), GEN),
43+
ShowLaws.laws(Const.show(Int.show()), Const.eq<Int, Int>(Eq.any()), GEN),
44+
FxLaws.laws<ConstPartialOf<Int>, Int>(GENK.genK(Gen.int()), GENK.genK(Gen.int()), EQK.liftEq(Int.eq()), ::const, ::const)
4245
)
43-
}
4446
}
4547
}

arrow-libs/core/arrow-core-data/src/test/kotlin/arrow/core/EitherTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class EitherTest : UnitSpec() {
7070
SemigroupKLaws.laws(Either.semigroupK(), Either.genK(Gen.id(Gen.int())), Either.eqK(Id.eq(Int.eq()))),
7171
HashLaws.laws(Either.hash(String.hash(), Int.hash()), GEN, Either.eq(String.eq(), Int.eq())),
7272
BicrosswalkLaws.laws(Either.bicrosswalk(), Either.genK2(), Either.eqK2()),
73-
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)
73+
FxLaws.laws<EitherPartialOf<String>, Int>(Gen.int().map(::Right), GEN.map { it }, Either.eqK(String.eq()).liftEq(Int.eq()), ::either, ::either)
7474
)
7575

7676
"empty should return a Right of the empty of the inner type" {

arrow-libs/core/arrow-core-data/src/test/kotlin/arrow/core/EvalTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class EvalTest : UnitSpec() {
3838

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

4444
"should map wrapped value" {

arrow-libs/core/arrow-core-data/src/test/kotlin/arrow/core/ValidatedTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class ValidatedTest : UnitSpec() {
5959
Validated.genK2(),
6060
Validated.eqK2()
6161
),
62-
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)
62+
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)
6363
)
6464

6565
"fold should call function on Invalid" {

arrow-libs/core/arrow-core-test/src/main/kotlin/arrow/core/test/laws/FxLaws.kt

+9-9
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@ object FxLaws {
2626
pureGen: Gen<Kind<F, A>>, // TODO cannot specify or filter a pure generator, so we need to require an additional one
2727
G: Gen<Kind<F, A>>,
2828
EQ: Eq<Kind<F, A>>,
29-
fxBlock: EagerFxBlock<F, A>,
30-
sfxBlock: SuspendFxBlock<F, A>
29+
fxEager: EagerFxBlock<F, A>,
30+
fxSuspend: SuspendFxBlock<F, A>
3131
): List<Law> = listOf(
32-
Law("non-suspended fx can bind immediate values") { nonSuspendedCanBindImmediate(G, EQ, fxBlock) },
33-
Law("non-suspended fx can bind immediate exceptions") { nonSuspendedCanBindImmediateException(pureGen, fxBlock) },
34-
Law("suspended fx can bind immediate values") { suspendedCanBindImmediateValues(G, EQ, sfxBlock) },
35-
Law("suspended fx can bind suspended values") { suspendedCanBindSuspendedValues(G, EQ, sfxBlock) },
36-
Law("suspended fx can bind immediate exceptions") { suspendedCanBindImmediateExceptions(pureGen, sfxBlock) },
37-
Law("suspended fx can bind suspended exceptions") { suspendedCanBindSuspendedExceptions(pureGen, sfxBlock) }
32+
Law("non-suspended fx can bind immediate values") { nonSuspendedCanBindImmediateValues(G, EQ, fxEager) },
33+
Law("non-suspended fx can bind immediate exceptions") { nonSuspendedCanBindImmediateException(pureGen, fxEager) },
34+
Law("suspended fx can bind immediate values") { suspendedCanBindImmediateValues(G, EQ, fxSuspend) },
35+
Law("suspended fx can bind suspended values") { suspendedCanBindSuspendedValues(G, EQ, fxSuspend) },
36+
Law("suspended fx can bind immediate exceptions") { suspendedCanBindImmediateExceptions(pureGen, fxSuspend) },
37+
Law("suspended fx can bind suspended exceptions") { suspendedCanBindSuspendedExceptions(pureGen, fxSuspend) }
3838
)
3939

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

0 commit comments

Comments
 (0)