Skip to content

Commit eea20f4

Browse files
authored
Merge pull request #122 from kategory/rr-monaderror-laws
MonadError Laws
2 parents 51426be + e89d772 commit eea20f4

File tree

6 files changed

+55
-4
lines changed

6 files changed

+55
-4
lines changed

kategory/src/main/kotlin/kategory/instances/EitherMonad.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package kategory
22

3-
class EitherMonad<L> : Monad<EitherF<L>> {
3+
open class EitherMonad<L> : Monad<EitherF<L>> {
44

55
override fun <A> pure(a: A): Either<L, A> = Either.Right(a)
66

@@ -20,4 +20,18 @@ class EitherMonad<L> : Monad<EitherF<L>> {
2020
}
2121
}
2222
}
23+
}
24+
25+
class EitherMonadError<L> : EitherMonad<L>(), MonadError<EitherF<L>, L> {
26+
27+
override fun <A> raiseError(e: L): Either<L, A> = Either.Left(e)
28+
29+
override fun <A> handleErrorWith(fa: HK<EitherF<L>, A>, f: (L) -> HK<EitherF<L>, A>): Either<L, A> {
30+
val fea = fa.ev()
31+
return when (fea) {
32+
is Either.Left -> f(fea.a).ev()
33+
is Either.Right -> fea
34+
}
35+
}
36+
2337
}

kategory/src/main/kotlin/kategory/typeclasses/ApplicativeError.kt

+8
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,13 @@ interface ApplicativeError<F, E> : Applicative<F>, Typeclass {
1515
}
1616
}
1717

18+
fun <F, A> ApplicativeError<F, Throwable>.catch(f: () -> A): HK<F, A> =
19+
try {
20+
pure(f())
21+
}
22+
catch (e: Throwable) {
23+
raiseError(e)
24+
}
25+
1826
inline fun <reified F, reified E> applicativeError(): ApplicativeError<F, E> =
1927
instance(InstanceParametrizedType(Monad::class.java, listOf(F::class.java, E::class.java)))

kategory/src/test/kotlin/kategory/data/EitherTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import org.junit.runner.RunWith
1111
class EitherTest : UnitSpec() {
1212
init {
1313

14-
testLaws(MonadLaws.laws(EitherMonad<Int>(), Eq()))
14+
testLaws(MonadErrorLaws.laws(EitherMonadError<Throwable>(), Eq()))
1515

1616
"map should modify value" {
1717
forAll { a: Int, b: String ->

kategory/src/test/kotlin/kategory/data/TryTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class TryTest : UnitSpec() {
1111

1212
init {
1313

14-
testLaws(MonadLaws.laws(Try, Eq()))
14+
testLaws(MonadErrorLaws.laws(Try, Eq()))
1515

1616
"invoke of any should be success" {
1717
Try.invoke { 1 } shouldBe Success(1)

kategory/src/test/kotlin/kategory/generators/Generators.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ inline fun <reified F, A> genApplicative(valueGen: Gen<A>, AP: Applicative<F> =
77
}
88

99
fun <A, B> genFunctionAToB(genB: Gen<B>): Gen<(A) -> B> = object : Gen<(A) -> B> {
10-
override fun generate(): (A) -> B {
10+
override fun generate(): (A) -> B {
1111
val v = genB.generate()
1212
return { a -> v }
1313
}
14+
}
15+
16+
fun genThrowable(): Gen<Throwable> = object : Gen<Throwable> {
17+
override fun generate(): Throwable =
18+
Gen.oneOf(listOf(RuntimeException(), NoSuchElementException(), IllegalArgumentException())).generate()
1419
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kategory
2+
3+
import io.kotlintest.properties.Gen
4+
import io.kotlintest.properties.forAll
5+
6+
object MonadErrorLaws {
7+
8+
inline fun <reified F> laws(M: MonadError<F, Throwable> = monadError<F, Throwable>(), EQ: Eq<HK<F, Int>>): List<Law> =
9+
MonadLaws.laws(M, EQ) + listOf(
10+
Law("Monad Error Laws: left zero", { monadErrorLeftZero(M, EQ) }),
11+
Law("Monad Error Laws: ensure consistency", { monadErrorEnsureConsistency(M, EQ) })
12+
)
13+
14+
inline fun <reified F> monadErrorLeftZero(M: MonadError<F, Throwable> = monadError<F, Throwable>(), EQ: Eq<HK<F, Int>>): Unit =
15+
forAll(genFunctionAToB<Int, HK<F, Int>>(genApplicative(Gen.int(), M)), genThrowable(), { f: (Int) -> HK<F, Int>, e: Throwable ->
16+
M.flatMap(M.raiseError<Int>(e), f).equalUnderTheLaw(M.raiseError<Int>(e), EQ)
17+
})
18+
19+
inline fun <reified F> monadErrorEnsureConsistency(M: MonadError<F, Throwable> = monadError<F, Throwable>(), EQ: Eq<HK<F, Int>>): Unit =
20+
forAll(genApplicative(Gen.int(), M), genThrowable(), genFunctionAToB<Int, Boolean>(Gen.bool()), { fa: HK<F, Int>, e: Throwable, p: (Int) -> Boolean ->
21+
M.ensure(fa, { e }, p).equalUnderTheLaw(M.flatMap(fa, { a -> if (p(a)) M.pure(a) else M.raiseError(e) }), EQ)
22+
})
23+
24+
}

0 commit comments

Comments
 (0)