From 136c020944cd7fdb014bcb57cbe0d9b5e0f516f9 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 01:21:24 +0100 Subject: [PATCH 01/14] Add monad defer laws ported from cats --- .../kotlin/arrow/test/laws/MonadDeferLaws.kt | 104 +++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index f8514846f37..9a135a133b7 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -2,12 +2,17 @@ package arrow.test.laws import arrow.Kind import arrow.core.* +import arrow.data.k import arrow.effects.data.internal.BindingCancellationException import arrow.effects.typeclasses.MonadDefer +import arrow.instances.list.foldable.foldLeft import arrow.test.concurrency.SideEffect import arrow.test.generators.genIntSmall import arrow.test.generators.genThrowable import arrow.typeclasses.Eq +import io.kotlintest.matchers.Matcher +import io.kotlintest.matchers.should +import io.kotlintest.matchers.shouldBe import io.kotlintest.properties.forAll import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.newSingleThreadContext @@ -31,9 +36,106 @@ object MonadDeferLaws { Law("Sync bind: bindingInContext cancellation before flatMap") { SC.inContextCancellationBefore(EQ) }, Law("Sync bind: bindingInContext cancellation after flatMap") { SC.inContextCancellationAfter(EQ) }, Law("Sync bind: bindingInContext throw equivalent to raiseError") { SC.inContextErrorThrow(EQERR) }, - Law("Sync bind: monad comprehensions binding in other threads equivalence") { SC.monadComprehensionsBindInContextEquivalent(EQ) } + Law("Sync bind: monad comprehensions binding in other threads equivalence") { SC.monadComprehensionsBindInContextEquivalent(EQ) }, + Law("Sync laws: delay constant equals pure") { SC.delayConstantEqualsPure(EQ) }, + Law("Sync laws: delay throw equals raiseError") { SC.delayThrowEqualsRaiseError(EQERR) }, + Law("Sync laws: defer constant equals pure") { SC.deferConstantEqualsPure(EQ) }, + Law("Sync laws: deferUnsafe constant right equals pure") { SC.deferUnsafeConstantRightEqualsPure(EQ) }, + Law("Sync laws: deferUnsafe constant left equals raiseError") { SC.deferUnsafeConstantLeftEqualsRaiseError(EQERR) }, + Law("Sync laws: propagate error through bind") { SC.propagateErrorsThroughBind(EQERR) }, + Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, + Law("Sync laws: bind suspends evaluation") { SC.bindSuspendsEvaluation(EQ) }, + Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, + Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated right binds") { SC.stackSafetyOverRepeatedRightBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated attempts") { SC.stackSafetyOverRepeatedAttempts(5000, EQ) }, + Law("Sync laws: stack safety over repeated maps") { SC.stackSafetyOnRepeatedMaps(5000, EQ) } ) + fun MonadDefer.delayConstantEqualsPure(EQ: Eq>): Unit { + forAll(genIntSmall()) { x -> + delay { x }.equalUnderTheLaw(just(x), EQ) + } + } + + fun MonadDefer.deferConstantEqualsPure(EQ: Eq>): Unit { + forAll(genIntSmall()) { x -> + defer { just(x) }.equalUnderTheLaw(just(x), EQ) + } + } + + fun MonadDefer.deferUnsafeConstantRightEqualsPure(EQ: Eq>): Unit { + forAll(genIntSmall()) { x -> + deferUnsafe { x.right() }.equalUnderTheLaw(just(x), EQ) + } + } + + fun MonadDefer.deferUnsafeConstantLeftEqualsRaiseError(EQERR: Eq>): Unit { + forFew(5, genThrowable()) { t -> + deferUnsafe { t.left() }.equalUnderTheLaw(raiseError(t), EQERR) + } + } + + fun MonadDefer.delayThrowEqualsRaiseError(EQERR: Eq>): Unit { + forFew(5, genThrowable()) { t -> + delay { throw t }.equalUnderTheLaw(raiseError(t), EQERR) + } + } + + fun MonadDefer.propagateErrorsThroughBind(EQERR: Eq>): Unit { + forFew(5, genThrowable()) { t -> + delay { throw t }.flatMap { a: Int -> just(a) }.equalUnderTheLaw(raiseError(t), EQERR) + } + } + + fun MonadDefer.delaySuspendsEvaluation(EQ: Eq>): Unit { + val sideEffect = SideEffect(counter = 0) + val df = delay { sideEffect.increment(); sideEffect.counter } + + sideEffect.counter shouldBe 0 + df.equalUnderTheLaw(just(1), EQ) shouldBe true + } + + fun MonadDefer.bindSuspendsEvaluation(EQ: Eq>): Unit { + val sideEffect = SideEffect(counter = 0) + val df = just(0).flatMap { sideEffect.increment(); just(sideEffect.counter) } + + sideEffect.counter shouldBe 0 + df.equalUnderTheLaw(just(1), EQ) shouldBe true + } + + fun MonadDefer.mapSuspendsEvaluation(EQ: Eq>): Unit { + val sideEffect = SideEffect(counter = 0) + val df = just(0).map { sideEffect.increment(); sideEffect.counter } + + sideEffect.counter shouldBe 0 + df.equalUnderTheLaw(just(1), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOverRepeatedLeftBinds(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().k().foldLeft(just(0)) { def, x -> + def.flatMap { just(x); } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOverRepeatedRightBinds(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldRight(just(iterations)) { x, def -> + lazy().flatMap { def } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOverRepeatedAttempts(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldLeft(just(0)) { def, x -> + def.attempt().map { x } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOnRepeatedMaps(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldLeft(just(0)) { def, x -> + def.map { x } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + fun MonadDefer.asyncBind(EQ: Eq>): Unit = forAll(genIntSmall(), genIntSmall(), genIntSmall()) { x: Int, y: Int, z: Int -> val (bound, _) = bindingCancellable { From 60df237678a0a1cdda052ceb9a097d648c4670da Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 11:07:01 +0100 Subject: [PATCH 02/14] Remove stack safety laws for now as I don't think they apply --- .../kotlin/arrow/test/laws/MonadDeferLaws.kt | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index 9a135a133b7..0d5d91de02d 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -45,11 +45,7 @@ object MonadDeferLaws { Law("Sync laws: propagate error through bind") { SC.propagateErrorsThroughBind(EQERR) }, Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: bind suspends evaluation") { SC.bindSuspendsEvaluation(EQ) }, - Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, - Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) }, - Law("Sync laws: stack safety over repeated right binds") { SC.stackSafetyOverRepeatedRightBinds(5000, EQ) }, - Law("Sync laws: stack safety over repeated attempts") { SC.stackSafetyOverRepeatedAttempts(5000, EQ) }, - Law("Sync laws: stack safety over repeated maps") { SC.stackSafetyOnRepeatedMaps(5000, EQ) } + Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) } ) fun MonadDefer.delayConstantEqualsPure(EQ: Eq>): Unit { @@ -112,30 +108,6 @@ object MonadDeferLaws { df.equalUnderTheLaw(just(1), EQ) shouldBe true } - fun MonadDefer.stackSafetyOverRepeatedLeftBinds(iterations: Int = 5000, EQ: Eq>): Unit { - (0..iterations).toList().k().foldLeft(just(0)) { def, x -> - def.flatMap { just(x); } - }.equalUnderTheLaw(just(iterations), EQ) shouldBe true - } - - fun MonadDefer.stackSafetyOverRepeatedRightBinds(iterations: Int = 5000, EQ: Eq>): Unit { - (0..iterations).toList().foldRight(just(iterations)) { x, def -> - lazy().flatMap { def } - }.equalUnderTheLaw(just(iterations), EQ) shouldBe true - } - - fun MonadDefer.stackSafetyOverRepeatedAttempts(iterations: Int = 5000, EQ: Eq>): Unit { - (0..iterations).toList().foldLeft(just(0)) { def, x -> - def.attempt().map { x } - }.equalUnderTheLaw(just(iterations), EQ) shouldBe true - } - - fun MonadDefer.stackSafetyOnRepeatedMaps(iterations: Int = 5000, EQ: Eq>): Unit { - (0..iterations).toList().foldLeft(just(0)) { def, x -> - def.map { x } - }.equalUnderTheLaw(just(iterations), EQ) shouldBe true - } - fun MonadDefer.asyncBind(EQ: Eq>): Unit = forAll(genIntSmall(), genIntSmall(), genIntSmall()) { x: Int, y: Int, z: Int -> val (bound, _) = bindingCancellable { From 8ac2aea178d67bd90cab0801db13f6b6f4d648e9 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 11:45:26 +0100 Subject: [PATCH 03/14] Add arbitrary sleep to catch any async coroutine executions --- .../kotlin/arrow/test/laws/MonadDeferLaws.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index 0d5d91de02d..d8ef2b1d2d9 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -43,6 +43,7 @@ object MonadDeferLaws { Law("Sync laws: deferUnsafe constant right equals pure") { SC.deferUnsafeConstantRightEqualsPure(EQ) }, Law("Sync laws: deferUnsafe constant left equals raiseError") { SC.deferUnsafeConstantLeftEqualsRaiseError(EQERR) }, Law("Sync laws: propagate error through bind") { SC.propagateErrorsThroughBind(EQERR) }, + Law("Sync laws: defer suspens evaluation") { SC.deferSuspendsEvaluation(EQ) }, Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: bind suspends evaluation") { SC.bindSuspendsEvaluation(EQ) }, Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) } @@ -84,10 +85,22 @@ object MonadDeferLaws { } } + fun MonadDefer.deferSuspendsEvaluation(EQ: Eq>): Unit { + val sideEffect = SideEffect(counter = 0) + val df = defer { sideEffect.increment(); just(sideEffect.counter) } + + Thread.sleep(10) + + sideEffect.counter shouldBe 0 + df.equalUnderTheLaw(just(1), EQ) shouldBe true + } + fun MonadDefer.delaySuspendsEvaluation(EQ: Eq>): Unit { val sideEffect = SideEffect(counter = 0) val df = delay { sideEffect.increment(); sideEffect.counter } + Thread.sleep(10) + sideEffect.counter shouldBe 0 df.equalUnderTheLaw(just(1), EQ) shouldBe true } @@ -96,6 +109,8 @@ object MonadDeferLaws { val sideEffect = SideEffect(counter = 0) val df = just(0).flatMap { sideEffect.increment(); just(sideEffect.counter) } + Thread.sleep(10) + sideEffect.counter shouldBe 0 df.equalUnderTheLaw(just(1), EQ) shouldBe true } @@ -104,6 +119,8 @@ object MonadDeferLaws { val sideEffect = SideEffect(counter = 0) val df = just(0).map { sideEffect.increment(); sideEffect.counter } + Thread.sleep(10) + sideEffect.counter shouldBe 0 df.equalUnderTheLaw(just(1), EQ) shouldBe true } From c335526406892a753b5c1952c1a27c1be2df3f45 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 12:51:08 +0100 Subject: [PATCH 04/14] change method name: bind => flatMap --- .../src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index d8ef2b1d2d9..eab87aa0288 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -45,7 +45,7 @@ object MonadDeferLaws { Law("Sync laws: propagate error through bind") { SC.propagateErrorsThroughBind(EQERR) }, Law("Sync laws: defer suspens evaluation") { SC.deferSuspendsEvaluation(EQ) }, Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, - Law("Sync laws: bind suspends evaluation") { SC.bindSuspendsEvaluation(EQ) }, + Law("Sync laws: flatMap suspends evaluation") { SC.flatMapSuspendsEvaluation(EQ) }, Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) } ) @@ -105,7 +105,7 @@ object MonadDeferLaws { df.equalUnderTheLaw(just(1), EQ) shouldBe true } - fun MonadDefer.bindSuspendsEvaluation(EQ: Eq>): Unit { + fun MonadDefer.flatMapSuspendsEvaluation(EQ: Eq>): Unit { val sideEffect = SideEffect(counter = 0) val df = just(0).flatMap { sideEffect.increment(); just(sideEffect.counter) } From 8a986ffb5ef108b5efc6b2a5ffd4569d9b8fb925 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 14:35:20 +0100 Subject: [PATCH 05/14] Add delay constant func and make async and invoke start lazy by default --- .../src/main/kotlin/arrow/effects/DeferredK.kt | 4 ++-- .../src/main/kotlin/arrow/effects/typeclasses/MonadDefer.kt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/effects/arrow-effects-kotlinx-coroutines/src/main/kotlin/arrow/effects/DeferredK.kt b/modules/effects/arrow-effects-kotlinx-coroutines/src/main/kotlin/arrow/effects/DeferredK.kt index 1e8c8d09b17..ca3ec3ae088 100644 --- a/modules/effects/arrow-effects-kotlinx-coroutines/src/main/kotlin/arrow/effects/DeferredK.kt +++ b/modules/effects/arrow-effects-kotlinx-coroutines/src/main/kotlin/arrow/effects/DeferredK.kt @@ -70,7 +70,7 @@ data class DeferredK(private val deferred: Deferred, val scope: Corout fun defer(scope: CoroutineScope = GlobalScope, ctx: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.LAZY, fa: () -> DeferredKOf): DeferredK = scope.asyncK(ctx, start) { fa().await() } - operator fun invoke(scope: CoroutineScope = GlobalScope, ctx: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.DEFAULT, f: () -> A): DeferredK = + operator fun invoke(scope: CoroutineScope = GlobalScope, ctx: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.LAZY, f: () -> A): DeferredK = scope.asyncK(ctx, start) { f() } fun failed(t: Throwable): DeferredK = @@ -86,7 +86,7 @@ data class DeferredK(private val deferred: Deferred, val scope: Corout * its [CoroutineContext] is set to [DefaultDispatcher] * and its [CoroutineStart] is [CoroutineStart.DEFAULT]. */ - fun async(scope: CoroutineScope = GlobalScope, ctx: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.DEFAULT, fa: Proc): DeferredK = + fun async(scope: CoroutineScope = GlobalScope, ctx: CoroutineContext = Dispatchers.Default, start: CoroutineStart = CoroutineStart.LAZY, fa: Proc): DeferredK = scope.asyncK(ctx, start) { CompletableDeferred().apply { fa { diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/typeclasses/MonadDefer.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/typeclasses/MonadDefer.kt index f4c9a37a0ec..2a54b3f5a4a 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/typeclasses/MonadDefer.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/typeclasses/MonadDefer.kt @@ -24,6 +24,8 @@ interface MonadDefer : MonadThrow, Bracket { } } + fun delay(fa: Kind): Kind = defer { fa } + @Deprecated("Use delay instead", ReplaceWith("delay(f)", "arrow.effects.typeclasses.MonadDefer")) operator fun invoke(f: () -> A): Kind = From c5fff871f0c5e9e4e0d9af0e0e0b8da765b07317 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 14:37:27 +0100 Subject: [PATCH 06/14] Remove delay override in async instances Monad defer already provides a default for it. And if async override differs in any way this would produce unexpected results --- .../src/main/kotlin/arrow/effects/instances/io.kt | 3 --- .../src/main/kotlin/arrow/effects/DeferredKInstances.kt | 3 --- 2 files changed, 6 deletions(-) diff --git a/modules/effects/arrow-effects-instances/src/main/kotlin/arrow/effects/instances/io.kt b/modules/effects/arrow-effects-instances/src/main/kotlin/arrow/effects/instances/io.kt index a00ec8d0ff7..d8ce7130017 100644 --- a/modules/effects/arrow-effects-instances/src/main/kotlin/arrow/effects/instances/io.kt +++ b/modules/effects/arrow-effects-instances/src/main/kotlin/arrow/effects/instances/io.kt @@ -101,9 +101,6 @@ interface IOAsyncInstance : Async, IOMonadDeferInstance { override fun IOOf.continueOn(ctx: CoroutineContext): IO = fix().continueOn(ctx) - - override fun delay(f: () -> A): IO = - IO.invoke(f) } @extension diff --git a/modules/effects/arrow-effects-kotlinx-coroutines-instances/src/main/kotlin/arrow/effects/DeferredKInstances.kt b/modules/effects/arrow-effects-kotlinx-coroutines-instances/src/main/kotlin/arrow/effects/DeferredKInstances.kt index 91e5f1fd4af..99fccc8fe87 100644 --- a/modules/effects/arrow-effects-kotlinx-coroutines-instances/src/main/kotlin/arrow/effects/DeferredKInstances.kt +++ b/modules/effects/arrow-effects-kotlinx-coroutines-instances/src/main/kotlin/arrow/effects/DeferredKInstances.kt @@ -92,9 +92,6 @@ interface DeferredKAsyncInstance : Async, DeferredKMonadDeferInsta override fun DeferredKOf.continueOn(ctx: CoroutineContext): DeferredK = fix().continueOn(ctx = ctx) - override fun delay(f: () -> A): DeferredK = - DeferredK.invoke(f = f) - override fun invoke(ctx: CoroutineContext, f: () -> A): Kind = DeferredK.invoke(ctx = ctx, f = f) } From b526432dfcb95e3425e1d68818906a9105cdf3da Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 14:58:44 +0100 Subject: [PATCH 07/14] Add test --- .../src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index eab87aa0288..bcf7af0e52a 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -46,7 +46,8 @@ object MonadDeferLaws { Law("Sync laws: defer suspens evaluation") { SC.deferSuspendsEvaluation(EQ) }, Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: flatMap suspends evaluation") { SC.flatMapSuspendsEvaluation(EQ) }, - Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) } + Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, + Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) } ) fun MonadDefer.delayConstantEqualsPure(EQ: Eq>): Unit { @@ -125,6 +126,13 @@ object MonadDeferLaws { df.equalUnderTheLaw(just(1), EQ) shouldBe true } + fun MonadDefer.repeatedSyncEvaluationNotMemoized(EQ: Eq>): Unit { + val sideEffect = SideEffect() + val df = delay { sideEffect.increment(); sideEffect.counter } + + df.flatMap { df }.flatMap { df }.equalUnderTheLaw(just(3), EQ) + } + fun MonadDefer.asyncBind(EQ: Eq>): Unit = forAll(genIntSmall(), genIntSmall(), genIntSmall()) { x: Int, y: Int, z: Int -> val (bound, _) = bindingCancellable { From 5ce80f33166df7dc4367247a9356fc1930e45834 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 20:55:16 +0100 Subject: [PATCH 08/14] Add stack safety laws back --- .../kotlin/arrow/test/laws/MonadDeferLaws.kt | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index bcf7af0e52a..1ea2cfa907b 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -47,7 +47,11 @@ object MonadDeferLaws { Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: flatMap suspends evaluation") { SC.flatMapSuspendsEvaluation(EQ) }, Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, - Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) } + Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) }, + Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated right binds") { SC.stackSafetyOverRepeatedRightBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated attempts") { SC.stackSafetyOverRepeatedAttempts(5000, EQ) }, + Law("Sync laws: stack safety over repeated maps") { SC.stackSafetyOnRepeatedMaps(5000, EQ) } ) fun MonadDefer.delayConstantEqualsPure(EQ: Eq>): Unit { @@ -133,6 +137,31 @@ object MonadDeferLaws { df.flatMap { df }.flatMap { df }.equalUnderTheLaw(just(3), EQ) } + fun MonadDefer.stackSafetyOverRepeatedLeftBinds(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().k().foldLeft(just(0)) { def, x -> + def.flatMap { just(x) } + } + .equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOverRepeatedRightBinds(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldRight(just(iterations)) { x, def -> + lazy().flatMap { def } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOverRepeatedAttempts(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldLeft(just(0)) { def, x -> + def.attempt().map { x } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + + fun MonadDefer.stackSafetyOnRepeatedMaps(iterations: Int = 5000, EQ: Eq>): Unit { + (0..iterations).toList().foldLeft(just(0)) { def, x -> + def.map { x } + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true + } + fun MonadDefer.asyncBind(EQ: Eq>): Unit = forAll(genIntSmall(), genIntSmall(), genIntSmall()) { x: Int, y: Int, z: Int -> val (bound, _) = bindingCancellable { From 91e7f682c3f6d09ef59fccae5d8cafe67fe4fa60 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 21 Nov 2018 22:52:21 +0100 Subject: [PATCH 09/14] Missing shouldBe true check --- .../src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index 1ea2cfa907b..a90f50d49d7 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -134,14 +134,13 @@ object MonadDeferLaws { val sideEffect = SideEffect() val df = delay { sideEffect.increment(); sideEffect.counter } - df.flatMap { df }.flatMap { df }.equalUnderTheLaw(just(3), EQ) + df.flatMap { df }.flatMap { df }.equalUnderTheLaw(just(3), EQ) shouldBe true } fun MonadDefer.stackSafetyOverRepeatedLeftBinds(iterations: Int = 5000, EQ: Eq>): Unit { (0..iterations).toList().k().foldLeft(just(0)) { def, x -> def.flatMap { just(x) } - } - .equalUnderTheLaw(just(iterations), EQ) shouldBe true + }.equalUnderTheLaw(just(iterations), EQ) shouldBe true } fun MonadDefer.stackSafetyOverRepeatedRightBinds(iterations: Int = 5000, EQ: Eq>): Unit { From c4bed037c345585580a1d613f45551b0005c5498 Mon Sep 17 00:00:00 2001 From: Jannis Date: Fri, 23 Nov 2018 00:06:12 +0100 Subject: [PATCH 10/14] Skip stack safety laws for reactor and rx2 integrations --- .../main/kotlin/arrow/test/laws/AsyncLaws.kt | 5 +++-- .../kotlin/arrow/test/laws/MonadDeferLaws.kt | 20 ++++++++++++------- .../test/kotlin/arrow/effects/FluxKTests.kt | 9 ++++++++- .../test/kotlin/arrow/effects/MonoKTests.kt | 8 +++++++- .../kotlin/arrow/effects/FlowableKTests.kt | 16 ++++++++++----- .../test/kotlin/arrow/effects/MaybeKTests.kt | 12 ++++++++--- .../kotlin/arrow/effects/ObservableKTests.kt | 9 +++++++-- .../test/kotlin/arrow/effects/SingleKTests.kt | 10 ++++++++-- 8 files changed, 66 insertions(+), 23 deletions(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/AsyncLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/AsyncLaws.kt index 677b5bd7f07..1ac820439b9 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/AsyncLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/AsyncLaws.kt @@ -18,9 +18,10 @@ object AsyncLaws { AC: Async, EQ: Eq>, EQ_EITHER: Eq>>, - EQERR: Eq> = EQ + EQERR: Eq> = EQ, + testStackSafety: Boolean = true ): List = - MonadDeferLaws.laws(AC, EQERR, EQ_EITHER) + listOf( + MonadDeferLaws.laws(AC, EQERR, EQ_EITHER, testStackSafety = testStackSafety) + listOf( Law("Async Laws: success equivalence") { AC.asyncSuccess(EQ) }, Law("Async Laws: error equivalence") { AC.asyncError(EQERR) }, Law("Async Laws: continueOn jumps threads") { AC.continueOn(EQ) }, diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index a90f50d49d7..2f7de04219a 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -23,7 +23,8 @@ object MonadDeferLaws { SC: MonadDefer, EQ: Eq>, EQ_EITHER: Eq>>, - EQERR: Eq> = EQ + EQERR: Eq> = EQ, + testStackSafety: Boolean = true ): List = BracketLaws.laws(SC, EQ, EQ_EITHER, EQERR) + listOf( Law("Sync bind: binding blocks") { SC.asyncBind(EQ) }, @@ -47,12 +48,17 @@ object MonadDeferLaws { Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: flatMap suspends evaluation") { SC.flatMapSuspendsEvaluation(EQ) }, Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, - Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) }, - Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) }, - Law("Sync laws: stack safety over repeated right binds") { SC.stackSafetyOverRepeatedRightBinds(5000, EQ) }, - Law("Sync laws: stack safety over repeated attempts") { SC.stackSafetyOverRepeatedAttempts(5000, EQ) }, - Law("Sync laws: stack safety over repeated maps") { SC.stackSafetyOnRepeatedMaps(5000, EQ) } - ) + Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) } + ) + if (testStackSafety) { + listOf( + Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated right binds") { SC.stackSafetyOverRepeatedRightBinds(5000, EQ) }, + Law("Sync laws: stack safety over repeated attempts") { SC.stackSafetyOverRepeatedAttempts(5000, EQ) }, + Law("Sync laws: stack safety over repeated maps") { SC.stackSafetyOnRepeatedMaps(5000, EQ) } + ) + } else { + emptyList() + } fun MonadDefer.delayConstantEqualsPure(EQ: Eq>): Unit { forAll(genIntSmall()) { x -> diff --git a/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/FluxKTests.kt b/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/FluxKTests.kt index c42e098749a..cda886b2a18 100644 --- a/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/FluxKTests.kt +++ b/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/FluxKTests.kt @@ -13,6 +13,8 @@ import arrow.test.laws.FoldableLaws import arrow.test.laws.TraverseLaws import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec +import io.kotlintest.TestCaseContext import io.kotlintest.matchers.shouldNotBe import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.not @@ -53,10 +55,15 @@ class FluxKTest : UnitSpec() { } } + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("FluxK: Skipping sync laws for stack safety because they are not supported. See https://github.com/reactor/reactor-core/issues/1441") + super.interceptSpec(context, spec) + } + init { testLaws( - AsyncLaws.laws(FluxK.async(), EQ(), EQ()), + AsyncLaws.laws(FluxK.async(), EQ(), EQ(), testStackSafety = false), FoldableLaws.laws(FluxK.foldable(), { FluxK.just(it) }, Eq.any()), TraverseLaws.laws(FluxK.traverse(), FluxK.functor(), { FluxK.just(it) }, EQ()) ) diff --git a/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/MonoKTests.kt b/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/MonoKTests.kt index ab6a96592c9..23ae4c75ea9 100644 --- a/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/MonoKTests.kt +++ b/modules/effects/arrow-effects-reactor-instances/src/test/kotlin/arrow/effects/MonoKTests.kt @@ -7,6 +7,7 @@ import arrow.test.UnitSpec import arrow.test.laws.AsyncLaws import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec import io.kotlintest.matchers.shouldNotBe import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.not @@ -47,9 +48,14 @@ class MonoKTest : UnitSpec() { } } + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("MonoK: Skipping sync laws for stack safety because they are not supported. See https://github.com/reactor/reactor-core/issues/1441") + super.interceptSpec(context, spec) + } + init { testLaws( - AsyncLaws.laws(MonoK.async(), EQ(), EQ()) + AsyncLaws.laws(MonoK.async(), EQ(), EQ(), testStackSafety = false) ) "Multi-thread Singles finish correctly" { diff --git a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/FlowableKTests.kt b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/FlowableKTests.kt index b3f3cf0c75b..f5eed1e9092 100644 --- a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/FlowableKTests.kt +++ b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/FlowableKTests.kt @@ -12,6 +12,7 @@ import arrow.test.laws.FoldableLaws import arrow.test.laws.TraverseLaws import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec import io.kotlintest.matchers.shouldNotBe import io.reactivex.Flowable import io.reactivex.schedulers.Schedulers @@ -46,29 +47,34 @@ class FlowableKTests : UnitSpec() { } + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("FlowableK: Skipping sync laws for stack safety because they are not supported. See https://github.com/ReactiveX/RxJava/issues/6322") + super.interceptSpec(context, spec) + } + init { - testLaws(AsyncLaws.laws(FlowableK.async(), EQ(), EQ())) + testLaws(AsyncLaws.laws(FlowableK.async(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(FlowableK.async(), EQ(), EQ())) //testLaws(AsyncLaws.laws(FlowableK.async(), EQ(), EQ())) - testLaws(AsyncLaws.laws(FlowableK.asyncDrop(), EQ(), EQ())) + testLaws(AsyncLaws.laws(FlowableK.asyncDrop(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(FlowableK.asyncDrop(), EQ(), EQ())) //testLaws(AsyncLaws.laws(FlowableK.asyncDrop(), EQ(), EQ())) - testLaws(AsyncLaws.laws(FlowableK.asyncError(), EQ(), EQ())) + testLaws(AsyncLaws.laws(FlowableK.asyncError(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(FlowableK.asyncError(), EQ(), EQ())) //testLaws(AsyncLaws.laws(FlowableK.asyncError(), EQ(), EQ())) - testLaws(AsyncLaws.laws(FlowableK.asyncLatest(), EQ(), EQ())) + testLaws(AsyncLaws.laws(FlowableK.asyncLatest(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(FlowableK.asyncLatest(), EQ(), EQ())) //testLaws(AsyncLaws.laws(FlowableK.asyncLatest(), EQ(), EQ())) - testLaws(AsyncLaws.laws(FlowableK.asyncMissing(), EQ(), EQ())) + testLaws(AsyncLaws.laws(FlowableK.asyncMissing(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(FlowableK.asyncMissing(), EQ(), EQ())) //testLaws(AsyncLaws.laws(FlowableK.asyncMissing(), EQ(), EQ())) diff --git a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/MaybeKTests.kt b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/MaybeKTests.kt index 2af0a4a93ac..ee9c7c48064 100644 --- a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/MaybeKTests.kt +++ b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/MaybeKTests.kt @@ -14,6 +14,7 @@ import arrow.test.UnitSpec import arrow.test.laws.* import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec import io.kotlintest.matchers.shouldBe import io.kotlintest.matchers.shouldNotBe import io.reactivex.Maybe @@ -47,6 +48,11 @@ class MaybeKTests : UnitSpec() { } + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("MaybeK: Skipping sync laws for stack safety because they are not supported. See https://github.com/ReactiveX/RxJava/issues/6322") + super.interceptSpec(context, spec) + } + init { testLaws( FunctorLaws.laws(MaybeK.functor(), { MaybeK.just(it) }, EQ()), @@ -55,9 +61,9 @@ class MaybeKTests : UnitSpec() { FoldableLaws.laws(MaybeK.foldable(), { MaybeK.just(it) }, Eq.any()), MonadErrorLaws.laws(MaybeK.monadError(), EQ(), EQ(), EQ()), ApplicativeErrorLaws.laws(MaybeK.applicativeError(), EQ(), EQ(), EQ()), - MonadDeferLaws.laws(MaybeK.monadDefer(), EQ(), EQ(), EQ()), - AsyncLaws.laws(MaybeK.async(), EQ(), EQ(), EQ()), - AsyncLaws.laws(MaybeK.effect(), EQ(), EQ(), EQ()) + MonadDeferLaws.laws(MaybeK.monadDefer(), EQ(), EQ(), EQ(), testStackSafety = false), + AsyncLaws.laws(MaybeK.async(), EQ(), EQ(), EQ(), testStackSafety = false), + AsyncLaws.laws(MaybeK.effect(), EQ(), EQ(), EQ(), testStackSafety = false) ) "Multi-thread Maybes finish correctly" { diff --git a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/ObservableKTests.kt b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/ObservableKTests.kt index d9ad610a045..e9a6dbc00f5 100644 --- a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/ObservableKTests.kt +++ b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/ObservableKTests.kt @@ -12,6 +12,7 @@ import arrow.test.laws.FoldableLaws import arrow.test.laws.TraverseLaws import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec import io.kotlintest.matchers.shouldNotBe import io.reactivex.Observable import io.reactivex.observers.TestObserver @@ -47,9 +48,13 @@ class ObservableKTest : UnitSpec() { } } - init { + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("ObservableK: Skipping sync laws for stack safety because they are not supported. See https://github.com/ReactiveX/RxJava/issues/6322") + super.interceptSpec(context, spec) + } - testLaws(AsyncLaws.laws(ObservableK.async(), EQ(), EQ())) + init { + testLaws(AsyncLaws.laws(ObservableK.async(), EQ(), EQ(), testStackSafety = false)) // FIXME(paco) #691 //testLaws(AsyncLaws.laws(ObservableK.async(), EQ(), EQ())) //testLaws(AsyncLaws.laws(ObservableK.async(), EQ(), EQ())) diff --git a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/SingleKTests.kt b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/SingleKTests.kt index 146491e1b48..1478dd8f7fc 100644 --- a/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/SingleKTests.kt +++ b/modules/effects/arrow-effects-rx2-instances/src/test/kotlin/arrow/effects/SingleKTests.kt @@ -12,6 +12,7 @@ import arrow.test.UnitSpec import arrow.test.laws.* import arrow.typeclasses.Eq import io.kotlintest.KTestJUnitRunner +import io.kotlintest.Spec import io.kotlintest.matchers.shouldNotBe import io.reactivex.Single import io.reactivex.observers.TestObserver @@ -44,6 +45,11 @@ class SingleKTests : UnitSpec() { } + override fun interceptSpec(context: Spec, spec: () -> Unit) { + println("SingleK: Skipping sync laws for stack safety because they are not supported. See https://github.com/ReactiveX/RxJava/issues/6322") + super.interceptSpec(context, spec) + } + init { testLaws( FunctorLaws.laws(SingleK.functor(), { SingleK.just(it) }, EQ()), @@ -51,8 +57,8 @@ class SingleKTests : UnitSpec() { MonadLaws.laws(SingleK.monad(), EQ()), MonadErrorLaws.laws(SingleK.monadError(), EQ(), EQ(), EQ()), ApplicativeErrorLaws.laws(SingleK.applicativeError(), EQ(), EQ(), EQ()), - AsyncLaws.laws(SingleK.async(), EQ(), EQ(), EQ()), - AsyncLaws.laws(SingleK.effect(), EQ(), EQ(), EQ()) + AsyncLaws.laws(SingleK.async(), EQ(), EQ(), EQ(), testStackSafety = false), + AsyncLaws.laws(SingleK.effect(), EQ(), EQ(), EQ(), testStackSafety = false) ) "Multi-thread Singles finish correctly" { From 4213da5b110f65a606935f7229cb39ba2361010b Mon Sep 17 00:00:00 2001 From: Jannis Date: Fri, 23 Nov 2018 16:32:31 +0100 Subject: [PATCH 11/14] Add some documentation for rx2 and reactor not being stack safe --- .../docs/docs/integrations/reactor/README.md | 43 +++++++++++++++++++ .../docs/docs/integrations/rx2/README.md | 43 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md index 0285a0456b9..001333a70f2 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md @@ -148,6 +148,49 @@ disposable() // Boom! caused by BindingCancellationException ``` +### Stack safety + +While [`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) usually guarantees stack safety, this does not apply for the reactor wrapper types. +This is a limitation on reactor's side. See the corresponding github [issue]({{ 'https://github.com/reactor/reactor-core/issues/1441' }}). + +To overcome this limitation and run code in a stack safe way, one can make use of `bindingStackSafe` which is provided for every instance of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) when you have `arrow-free` included. + +{: data-executable='true'} +```kotlin:ank +import arrow.Kind +import arrow.effects.MonoK +import arrow.effects.ForMonoK +import arrow.effects.fix +import arrow.effects.monok.monad.monad +import arrow.effects.monok.applicativeError.attempt +import arrow.free.bindingStackSafe +import arrow.free.run + +fun main() { + //sampleStart + // This will not result in a stack overflow + val result = MonoK.monad().bindingStackSafe { + (1..50000).fold(just(0)) { acc: Kind, x: Int -> + just(acc.bind() + 1) + }.bind() + }.run(MonoK.monad()).attempt() + //sampleEnd + println(result.fix().mono.block()!!) +} +``` + +```kotlin:ank +import arrow.core.Try +// This will result in a stack overflow +Try { + MonoK.monad().binding { + (1..50000).fold(just(0)) { acc: Kind, x: Int -> + just(acc.bind() + 1) + }.bind() + }.fix().mono.block() +} +``` + ### Supported Type Classes ```kotlin:ank:replace diff --git a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md index 5bd88ba480d..b6ba96fdeae 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md @@ -181,6 +181,49 @@ disposable() // Boom! caused by BindingCancellationException ``` +### Stack safety + +While [`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) usually guarantees stack safety, this does not apply for the rx2 wrapper types. +This is a limitation on rx2's side. See the corresponding github [issue]({{ 'https://github.com/ReactiveX/RxJava/issues/6322' }}). + +To overcome this limitation and run code in a stack safe way, one can make use of `bindingStackSafe` which is provided for every instance of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) when you have `arrow-free` included. + +{: data-executable='true'} +```kotlin:ank +import arrow.Kind +import arrow.effects.FlowableK +import arrow.effects.ForFlowableK +import arrow.effects.fix +import arrow.effects.flowablek.monad.monad +import arrow.effects.flowablek.applicativeError.attempt +import arrow.free.bindingStackSafe +import arrow.free.run + +fun main() { + //sampleStart + // This will not result in a stack overflow + val result = FlowableK.monad().bindingStackSafe { + (1..50000).fold(just(0)) { acc: Kind, x: Int -> + just(acc.bind() + 1) + }.bind() + }.run(FlowableK.monad()).attempt() + //sampleEnd + println(result.fix().flowable.blockingFirst()!!) +} +``` + +```kotlin:ank +import arrow.core.Try +// This will result in a stack overflow +Try { + FlowableK.monad().binding { + (1..50000).fold(just(0)) { acc: Kind, x: Int -> + just(acc.bind() + 1) + }.bind() + }.fix().flowable.blockingFirst() +} +``` + ### Supported Type Classes ```kotlin:ank:replace From 03d6fc5a8fec4aa0102237c46d6930cea253be95 Mon Sep 17 00:00:00 2001 From: Jannis Date: Fri, 23 Nov 2018 17:41:23 +0100 Subject: [PATCH 12/14] Slight changes, remove attempt --- .../docs/arrow-docs/docs/docs/integrations/reactor/README.md | 3 ++- modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md index 001333a70f2..d3e89310d3d 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md @@ -173,7 +173,7 @@ fun main() { (1..50000).fold(just(0)) { acc: Kind, x: Int -> just(acc.bind() + 1) }.bind() - }.run(MonoK.monad()).attempt() + }.run(MonoK.monad()) //sampleEnd println(result.fix().mono.block()!!) } @@ -181,6 +181,7 @@ fun main() { ```kotlin:ank import arrow.core.Try + // This will result in a stack overflow Try { MonoK.monad().binding { diff --git a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md index b6ba96fdeae..539b712c28e 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md @@ -206,7 +206,7 @@ fun main() { (1..50000).fold(just(0)) { acc: Kind, x: Int -> just(acc.bind() + 1) }.bind() - }.run(FlowableK.monad()).attempt() + }.run(FlowableK.monad()) //sampleEnd println(result.fix().flowable.blockingFirst()!!) } @@ -215,6 +215,7 @@ fun main() { ```kotlin:ank import arrow.core.Try // This will result in a stack overflow + Try { FlowableK.monad().binding { (1..50000).fold(just(0)) { acc: Kind, x: Int -> From 475625396f51464a5846de7c90204e114ece3880 Mon Sep 17 00:00:00 2001 From: Jannis Date: Fri, 23 Nov 2018 17:50:12 +0100 Subject: [PATCH 13/14] Fix links and names --- .../docs/arrow-docs/docs/docs/integrations/reactor/README.md | 4 ++-- modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md index d3e89310d3d..6c8cc701a03 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/reactor/README.md @@ -59,7 +59,7 @@ mono.value() The library provides instances of [`MonadError`]({{ '/docs/typeclasses/monaderror' | relative_url }}) and [`MonadDefer`]({{ '/docs/effects/monaddefer' | relative_url }}). -[`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) allows you to generify over datatypes that can run asynchronous code. You can use it with `FluxK` or `MonoK`. +[`Async`]({{ '/docs/effects/async' | relative_url }}) allows you to generify over datatypes that can run asynchronous code. You can use it with `FluxK` or `MonoK`. ```kotlin fun getSongUrlAsync(MS: MonadDefer) = @@ -150,7 +150,7 @@ disposable() ### Stack safety -While [`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) usually guarantees stack safety, this does not apply for the reactor wrapper types. +While [`MonadDefer`]({{ '/docs/effects/monaddefer' | relative_url }}) usually guarantees stack safety, this does not apply for the reactor wrapper types. This is a limitation on reactor's side. See the corresponding github [issue]({{ 'https://github.com/reactor/reactor-core/issues/1441' }}). To overcome this limitation and run code in a stack safe way, one can make use of `bindingStackSafe` which is provided for every instance of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) when you have `arrow-free` included. diff --git a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md index 539b712c28e..48f4dcebc2e 100644 --- a/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md +++ b/modules/docs/arrow-docs/docs/docs/integrations/rx2/README.md @@ -87,7 +87,7 @@ subject.value() The library provides instances of [`MonadError`]({{ '/docs/typeclasses/monaderror' | relative_url }}) and [`MonadDefer`]({{ '/docs/effects/monaddefer' | relative_url }}). -[`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) allows you to generify over datatypes that can run asynchronous code. You can use it with `ObservableK`, `FlowableK` or `SingleK`. +[`Async`]({{ '/docs/effects/async' | relative_url }}) allows you to generify over datatypes that can run asynchronous code. You can use it with `ObservableK`, `FlowableK` or `SingleK`. ```kotlin fun getSongUrlAsync(MS: MonadDefer) = @@ -183,7 +183,7 @@ disposable() ### Stack safety -While [`MonadDefer`]({{ '/docs/effects/async' | relative_url }}) usually guarantees stack safety, this does not apply for the rx2 wrapper types. +While [`MonadDefer`]({{ '/docs/effects/monaddefer' | relative_url }}) usually guarantees stack safety, this does not apply for the rx2 wrapper types. This is a limitation on rx2's side. See the corresponding github [issue]({{ 'https://github.com/ReactiveX/RxJava/issues/6322' }}). To overcome this limitation and run code in a stack safe way, one can make use of `bindingStackSafe` which is provided for every instance of [`Monad`]({{ '/docs/typeclasses/monad' | relative_url }}) when you have `arrow-free` included. From 5dc5bf58d0869e4e6e8d3b6e8be15b01b9bdc260 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sat, 24 Nov 2018 00:28:45 +0100 Subject: [PATCH 14/14] Comment out memoize test for now and link ticket to fix it If no one creates a ticket the next few minutes I might not have to change the id :) --- .../src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt index 2f7de04219a..37383986256 100644 --- a/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt +++ b/modules/core/arrow-test/src/main/kotlin/arrow/test/laws/MonadDeferLaws.kt @@ -47,8 +47,11 @@ object MonadDeferLaws { Law("Sync laws: defer suspens evaluation") { SC.deferSuspendsEvaluation(EQ) }, Law("Sync laws: delay suspends evaluation") { SC.delaySuspendsEvaluation(EQ) }, Law("Sync laws: flatMap suspends evaluation") { SC.flatMapSuspendsEvaluation(EQ) }, - Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) }, + Law("Sync laws: map suspends evaluation") { SC.mapSuspendsEvaluation(EQ) } + /* + FIXME: https://github.com/arrow-kt/arrow/issues/1156 - DeferredK fails this because coroutines only run once and then memoize results Law("Sync laws: Repeated evaluation not memoized") { SC.repeatedSyncEvaluationNotMemoized(EQ) } + */ ) + if (testStackSafety) { listOf( Law("Sync laws: stack safety over repeated left binds") { SC.stackSafetyOverRepeatedLeftBinds(5000, EQ) },