-
Notifications
You must be signed in to change notification settings - Fork 454
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
Add fixes to Monads found by new Law checks #120
Changes from all commits
13c97c3
4e8759f
5fe2f0d
3f592fd
5c12196
1953db9
6351002
0d3c3ff
45f6d97
52b94ad
fa11014
5190ae4
795d45e
f4fa45c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package kategory | ||
|
||
interface Eq<in F> : Typeclass { | ||
fun eqv(a: F, b: F): Boolean | ||
|
||
fun neqv(a: F, b: F): Boolean = | ||
!eqv(a, b) | ||
|
||
companion object { | ||
operator fun <F> invoke() = object : Eq<F> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be a default instance of |
||
override fun eqv(a: F, b: F): Boolean = | ||
a == b | ||
|
||
override fun neqv(a: F, b: F): Boolean = | ||
a != b | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith | |
class EitherTTest : UnitSpec() { | ||
init { | ||
|
||
testLaws(MonadLaws.laws(EitherTMonad<Id.F, Int>())) | ||
testLaws(MonadLaws.laws(EitherTMonad<Id.F, Int>(), Eq())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably better named as |
||
|
||
"map should modify value" { | ||
forAll { a: String -> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,10 @@ import org.junit.runner.RunWith | |
class EvalTest : UnitSpec() { | ||
init { | ||
|
||
testLaws(MonadLaws.laws(Eval)) | ||
testLaws(MonadLaws.laws(Eval, object : Eq<HK<Eval.F, Int>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have this instance in the core not just in tests and there should be an instance for each one of the datatypes we implement. |
||
override fun eqv(a: HK<Eval.F, Int>, b: HK<Eval.F, Int>): Boolean = | ||
a.ev().value() == b.ev().value() | ||
})) | ||
|
||
"should map wrapped value" { | ||
val sideEffect = SideEffect() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,10 @@ import org.junit.runner.RunWith | |
class Function0Test : UnitSpec() { | ||
init { | ||
|
||
testLaws(MonadLaws.laws(Function0)) | ||
testLaws(MonadLaws.laws(Function0, object : Eq<HK<Function0.F, Int>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have this instance in the core not just in tests. |
||
override fun eqv(a: HK<Function0.F, Int>, b: HK<Function0.F, Int>): Boolean = | ||
a.ev()() == b.ev()() | ||
})) | ||
|
||
"Function0Monad.binding should for comprehend over all values of multiple Function0" { | ||
Function0.binding { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,5 @@ package kategory | |
|
||
data class Law(val name: String, val test: () -> Unit) | ||
|
||
inline fun <reified A> A.equalUnderTheLaw(b: A, eq: Eq<A> = Eq()): Boolean = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have extension functions that are |
||
eq.eqv(this, b) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,47 @@ | ||
package kategory | ||
|
||
import io.kotlintest.matchers.shouldBe | ||
import io.kotlintest.properties.Gen | ||
import io.kotlintest.properties.forAll | ||
|
||
object MonadLaws { | ||
|
||
inline fun <reified F> laws(M: Monad<F> = monad<F>()): List<Law> = | ||
ApplicativeLaws.laws(M) + listOf( | ||
Law("Monad Laws: left identity", { leftIdentity(M) }), | ||
Law("Monad Laws: right identity", { rightIdentity(M) }), | ||
Law("Monad Laws: kleisli left identity", { kleisliLeftIdentity(M) }), | ||
Law("Monad Laws: kleisli right identity", { kleisliRightIdentity(M) }), | ||
Law("Monad Laws: map / flatMap coherence", { mapFlatMapCoherence(M) }), | ||
Law("Monad / JVM: stack safe", { mapFlatMapCoherence(M) }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! |
||
inline fun <reified F> laws(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): List<Law> = | ||
ApplicativeLaws.laws(M, EQ) + listOf( | ||
Law("Monad Laws: left identity", { leftIdentity(M, EQ) }), | ||
Law("Monad Laws: right identity", { rightIdentity(M, EQ) }), | ||
Law("Monad Laws: kleisli left identity", { kleisliLeftIdentity(M, EQ) }), | ||
Law("Monad Laws: kleisli right identity", { kleisliRightIdentity(M, EQ) }), | ||
Law("Monad Laws: map / flatMap coherence", { mapFlatMapCoherence(M, EQ) }), | ||
Law("Monad / JVM: stack safe", { stackSafety(5000, M) }) | ||
) | ||
|
||
inline fun <reified F> leftIdentity(M: Monad<F> = monad<F>()): Unit = | ||
inline fun <reified F> leftIdentity(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): Unit = | ||
forAll(genFunctionAToB<Int, HK<F, Int>>(genApplicative(Gen.int(), M)), Gen.int(), { f: (Int) -> HK<F, Int>, a: Int -> | ||
M.flatMap(M.pure(a), f) == f(a) | ||
M.flatMap(M.pure(a), f).equalUnderTheLaw(f(a), EQ) | ||
}) | ||
|
||
inline fun <reified F> rightIdentity(M: Monad<F> = monad<F>()): Unit = | ||
inline fun <reified F> rightIdentity(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): Unit = | ||
forAll(genApplicative(Gen.int(), M), { fa: HK<F, Int> -> | ||
M.flatMap(fa, { M.pure(it) }) == fa | ||
M.flatMap(fa, { M.pure(it) }).equalUnderTheLaw(fa, EQ) | ||
}) | ||
|
||
inline fun <reified F> kleisliLeftIdentity(M: Monad<F> = monad<F>()): Unit = | ||
inline fun <reified F> kleisliLeftIdentity(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): Unit = | ||
forAll(genFunctionAToB<Int, HK<F, Int>>(genApplicative(Gen.int(), M)), Gen.int(), { f: (Int) -> HK<F, Int>, a: Int -> | ||
(Kleisli({ n : Int -> M.pure(n)}, M) andThen Kleisli(f, M)).run(a) == f(a) | ||
(Kleisli({ n: Int -> M.pure(n) }, M) andThen Kleisli(f, M)).run(a).equalUnderTheLaw(f(a), EQ) | ||
}) | ||
|
||
inline fun <reified F> kleisliRightIdentity(M: Monad<F> = monad<F>()): Unit = | ||
inline fun <reified F> kleisliRightIdentity(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): Unit = | ||
forAll(genFunctionAToB<Int, HK<F, Int>>(genApplicative(Gen.int(), M)), Gen.int(), { f: (Int) -> HK<F, Int>, a: Int -> | ||
(Kleisli(f, M) andThen Kleisli({ n : Int -> M.pure(n)}, M)).run(a) == f(a) | ||
(Kleisli(f, M) andThen Kleisli({ n: Int -> M.pure(n) }, M)).run(a).equalUnderTheLaw(f(a), EQ) | ||
}) | ||
|
||
inline fun <reified F> mapFlatMapCoherence(M: Monad<F> = monad<F>()): Unit = | ||
inline fun <reified F> mapFlatMapCoherence(M: Monad<F> = monad<F>(), EQ: Eq<HK<F, Int>>): Unit = | ||
forAll(genFunctionAToB<Int, Int>(Gen.int()), genApplicative(Gen.int(), M), { f: (Int) -> Int, fa: HK<F, Int> -> | ||
M.flatMap(fa, { M.pure(f(it))}) == M.map(fa, f) | ||
M.flatMap(fa, { M.pure(f(it)) }).equalUnderTheLaw(M.map(fa, f), EQ) | ||
}) | ||
|
||
inline fun <reified F> stackSafety(iterations : Int = 5000, M: Monad<F> = monad<F>()): Unit { | ||
val res = M.tailRecM(0, { i -> M.pure(if (i < iterations) Either.Left(i + 1) else Either.Right(i))}) | ||
res shouldBe iterations | ||
inline fun <reified F> stackSafety(iterations: Int = 5000, M: Monad<F> = monad<F>()): Unit { | ||
val res = M.tailRecM(0, { i -> M.pure(if (i < iterations) Either.Left(i + 1) else Either.Right(i)) }) | ||
res.equalUnderTheLaw(iterations) | ||
} | ||
|
||
} |
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.
👏