-
Notifications
You must be signed in to change notification settings - Fork 451
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70 from FineCinnamon/rr-free
Free + stack safe monads
- Loading branch information
Showing
14 changed files
with
278 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package katz | ||
|
||
typealias FreeKind<S, A> = HK2<Free.F, S, A> | ||
typealias FreeF<S> = HK<Free.F, S> | ||
|
||
fun <S, A> FreeKind<S, A>.ev(): Free<S, A> = this as Free<S, A> | ||
|
||
sealed class Free<out S, out A> : FreeKind<S, A> { | ||
|
||
class F private constructor() | ||
|
||
companion object { | ||
fun <S, A> pure(a: A): Free<S, A> = Pure(a) | ||
fun <S, A> liftF(fa: HK<S, A>): Free<S, A> = Suspend(fa) | ||
} | ||
|
||
data class Pure<out S, out A>(val a: A) : Free<S, A>() | ||
data class Suspend<out S, out A>(val a: HK<S, A>) : Free<S, A>() | ||
data class FlatMapped<out S, out B, C>(val c: Free<S, C>, val f: (C) -> Free<S, B>) : Free<S, B>() | ||
|
||
override fun toString(): String = "Free(...) : toString is not stack-safe" | ||
} | ||
|
||
fun <S, A, B> Free<S, A>.map(f: (A) -> B): Free<S, B> = | ||
flatMap { Free.Pure<S, B>(f(it)) } | ||
|
||
fun <S, A, B> Free<S, A>.flatMap(f: (A) -> Free<S, B>): Free<S, B> = | ||
Free.FlatMapped(this, f) | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
tailrec fun <S, A> Free<S, A>.step(): Free<S, A> = | ||
if (this is Free.FlatMapped<S, A, *> && this.c is Free.FlatMapped<S, *, *>) { | ||
val g = this.f as (A) -> Free<S, A> | ||
val c = this.c.c as Free<S, A> | ||
val f = this.c.f as (A) -> Free<S, A> | ||
c.flatMap { cc -> f(cc).flatMap(g) }.step() | ||
} else if (this is Free.FlatMapped<S, A, *> && this.c is Free.Pure<S, *>) { | ||
val a = this.c.a as A | ||
val f = this.f as (A) -> Free<S, A> | ||
f(a).step() | ||
} else { | ||
this | ||
} | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
fun <M, S, A> Free<S, A>.foldMap(MM: Monad<M>, f: FunctionK<S, M>): HK<M, A> = | ||
MM.tailRecM(this) { | ||
val x = it.step() | ||
when (x) { | ||
is Free.Pure<S, A> -> MM.pure(Either.Right(x.a)) | ||
is Free.Suspend<S, A> -> MM.map(f(x.a), { Either.Right(it) }) | ||
is Free.FlatMapped<S, A, *> -> { | ||
val g = (x.f as (A) -> Free<S, A>) | ||
val c = x.c as Free<S, A> | ||
MM.map(c.foldMap(MM, f), { cc -> Either.Left(g(cc)) }) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package katz | ||
|
||
interface FreeMonad<S> : Monad<FreeF<S>>, Typeclass { | ||
override fun <A> pure(a: A): Free<S, A> = | ||
Free.pure(a) | ||
|
||
override fun <A, B> map(fa: FreeKind<S, A>, f: (A) -> B): HK<FreeF<S>, B> = | ||
fa.ev().map(f) | ||
|
||
override fun <A, B> flatMap(fa: FreeKind<S, A>, f: (A) -> FreeKind<S, B>): Free<S, B> = | ||
fa.ev().flatMap { f(it).ev() } | ||
|
||
override fun <A, B> tailRecM(a: A, f: (A) -> FreeKind<S, Either<A, B>>): Free<S, B> { | ||
return f(a).ev().flatMap { | ||
when (it) { | ||
is Either.Left -> tailRecM(it.a, f) | ||
is Either.Right -> pure(it.b) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,24 @@ | ||
package katz | ||
|
||
class OptionTMonad<F>(val MF : Monad<F>) : Monad<OptionTF<F>> { | ||
class OptionTMonad<F>(val MF: Monad<F>) : Monad<OptionTF<F>> { | ||
override fun <A> pure(a: A): OptionT<F, A> = OptionT(MF, MF.pure(Option(a))) | ||
|
||
override fun <A, B> flatMap(fa: OptionTKind<F, A>, f: (A) -> OptionTKind<F, B>): OptionT<F, B> = | ||
fa.ev().flatMap { f(it).ev() } | ||
|
||
override fun <A, B> map(fa: OptionTKind<F, A>, f: (A) -> B): OptionT<F, B> = | ||
fa.ev().map(f) | ||
|
||
override fun <A, B> tailRecM(a: A, f: (A) -> HK<OptionTF<F>, Either<A, B>>): OptionT<F, B> = | ||
OptionT(MF, MF.tailRecM(a, { | ||
MF.map(f(it).ev().value, { | ||
it.fold({ | ||
Either.Right<Option<B>>(Option.None) | ||
}, { | ||
it.map { Option.Some(it) } | ||
}) | ||
}) | ||
})) | ||
} | ||
|
||
fun <F, A> OptionTKind<F, A>.ev(): OptionT<F, A> = this as OptionT<F, A> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package katz | ||
|
||
import io.kotlintest.KTestJUnitRunner | ||
import io.kotlintest.matchers.shouldBe | ||
import org.junit.runner.RunWith | ||
|
||
sealed class Ops<A> : HK<Ops.F, A> { | ||
|
||
class F private constructor() | ||
|
||
data class Value(val a: Int) : Ops<Int>() | ||
data class Add(val a: Int, val y: Int) : Ops<Int>() | ||
data class Subtract(val a: Int, val y: Int) : Ops<Int>() | ||
|
||
companion object : FreeMonad<Ops.F> { | ||
fun value(n: Int): Free<Ops.F, Int> = Free.liftF(Ops.Value(n)) | ||
fun add(n: Int, y: Int): Free<Ops.F, Int> = Free.liftF(Ops.Add(n, y)) | ||
fun subtract(n: Int, y: Int): Free<Ops.F, Int> = Free.liftF(Ops.Subtract(n, y)) | ||
} | ||
} | ||
|
||
fun <A> HK<Ops.F, A>.ev(): Ops<A> = this as Ops<A> | ||
|
||
val optionInterpreter: FunctionK<Ops.F, Option.F> = object : FunctionK<Ops.F, Option.F> { | ||
override fun <A> invoke(fa: HK<Ops.F, A>): Option<A> { | ||
val op = fa.ev() | ||
return when (op) { | ||
is Ops.Add -> Option.Some(op.a + op.y) | ||
is Ops.Subtract -> Option.Some(op.a - op.y) | ||
is Ops.Value -> Option.Some(op.a) | ||
} as Option<A> | ||
} | ||
} | ||
|
||
val nonEmptyListInterpter: FunctionK<Ops.F, NonEmptyList.F> = object : FunctionK<Ops.F, NonEmptyList.F> { | ||
override fun <A> invoke(fa: HK<Ops.F, A>): NonEmptyList<A> { | ||
val op = fa.ev() | ||
return when (op) { | ||
is Ops.Add -> NonEmptyList.of(op.a + op.y) | ||
is Ops.Subtract -> NonEmptyList.of(op.a - op.y) | ||
is Ops.Value -> NonEmptyList.of(op.a) | ||
} as NonEmptyList<A> | ||
} | ||
} | ||
|
||
val idInterpreter: FunctionK<Ops.F, Id.F> = object : FunctionK<Ops.F, Id.F> { | ||
override fun <A> invoke(fa: HK<Ops.F, A>): Id<A> { | ||
val op = fa.ev() | ||
return when (op) { | ||
is Ops.Add -> Id(op.a + op.y) | ||
is Ops.Subtract -> Id(op.a - op.y) | ||
is Ops.Value -> Id(op.a) | ||
} as Id<A> | ||
} | ||
} | ||
|
||
@RunWith(KTestJUnitRunner::class) | ||
class FreeTest : UnitSpec() { | ||
|
||
val program = Ops.binding { | ||
val added = !Ops.add(10, 10) | ||
val substracted = !Ops.subtract(added, 50) | ||
yields(substracted) | ||
}.ev() | ||
|
||
fun stackSafeTestProgram(n: Int, stopAt: Int): Free<Ops.F, Int> = Ops.binding { | ||
val v = !Ops.add(n, 1) | ||
val r = !if (v < stopAt) stackSafeTestProgram(v, stopAt) else Free.pure<Ops.F, Int>(v) | ||
yields(r) | ||
}.ev() | ||
|
||
init { | ||
|
||
"Can interpret an ADT as Free operations" { | ||
program.foldMap(Option, optionInterpreter).ev() shouldBe Option.Some(-30) | ||
program.foldMap(Id, idInterpreter).ev() shouldBe Id(-30) | ||
program.foldMap(NonEmptyList, nonEmptyListInterpter).ev() shouldBe NonEmptyList.of(-30) | ||
} | ||
|
||
"foldMap is stack safe" { | ||
val n = 50000 | ||
val hugeProg = stackSafeTestProgram(0, n) | ||
hugeProg.foldMap(Id, idInterpreter).value() shouldBe n | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.