Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monad defer laws #1150

Merged
merged 17 commits into from Nov 24, 2018
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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
Expand All @@ -31,9 +36,95 @@ 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: 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) }
)

fun <F> MonadDefer<F>.delayConstantEqualsPure(EQ: Eq<Kind<F, Int>>): Unit {
forAll(genIntSmall()) { x ->
delay { x }.equalUnderTheLaw(just(x), EQ)
}
}

fun <F> MonadDefer<F>.deferConstantEqualsPure(EQ: Eq<Kind<F, Int>>): Unit {
forAll(genIntSmall()) { x ->
defer { just(x) }.equalUnderTheLaw(just(x), EQ)
}
}

fun <F> MonadDefer<F>.deferUnsafeConstantRightEqualsPure(EQ: Eq<Kind<F, Int>>): Unit {
forAll(genIntSmall()) { x ->
deferUnsafe { x.right() }.equalUnderTheLaw(just(x), EQ)
}
}

fun <F> MonadDefer<F>.deferUnsafeConstantLeftEqualsRaiseError(EQERR: Eq<Kind<F, Int>>): Unit {
forFew(5, genThrowable()) { t ->
deferUnsafe { t.left() }.equalUnderTheLaw(raiseError(t), EQERR)
}
}

fun <F> MonadDefer<F>.delayThrowEqualsRaiseError(EQERR: Eq<Kind<F, Int>>): Unit {
forFew(5, genThrowable()) { t ->
delay { throw t }.equalUnderTheLaw(raiseError(t), EQERR)
}
}

fun <F> MonadDefer<F>.propagateErrorsThroughBind(EQERR: Eq<Kind<F, Int>>): Unit {
forFew(5, genThrowable()) { t ->
delay { throw t }.flatMap<Int, Int> { a: Int -> just(a) }.equalUnderTheLaw(raiseError(t), EQERR)
}
}

fun <F> MonadDefer<F>.deferSuspendsEvaluation(EQ: Eq<Kind<F, Int>>): 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 <F> MonadDefer<F>.delaySuspendsEvaluation(EQ: Eq<Kind<F, Int>>): 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
}

fun <F> MonadDefer<F>.bindSuspendsEvaluation(EQ: Eq<Kind<F, Int>>): Unit {
1Jajen1 marked this conversation as resolved.
Show resolved Hide resolved
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
}

fun <F> MonadDefer<F>.mapSuspendsEvaluation(EQ: Eq<Kind<F, Int>>): Unit {
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
}

fun <F> MonadDefer<F>.asyncBind(EQ: Eq<Kind<F, Int>>): Unit =
forAll(genIntSmall(), genIntSmall(), genIntSmall()) { x: Int, y: Int, z: Int ->
val (bound, _) = bindingCancellable {
Expand Down