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

Trampoline #77

Merged
merged 9 commits into from
Jul 25, 2017
23 changes: 23 additions & 0 deletions kategory/src/main/kotlin/kategory/data/Trampoline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kategory

/**
* Trampoline is often used to emulate tail recursion. The idea is to have some step code that can be trampolined itself
* to emulate recursion. The difference with standard recursion would be that there is no need to rewind the whole stack
* when we reach the end of the stack, since the first value returned that is not a trampoline would be directly
* returned as the overall result value for the whole function chain. That means Trampoline emulates what tail recursion
* does.
*/
typealias TrampolineF<A> = Free<Function0.F, A>

object Trampoline : TrampolineFunctions

interface TrampolineFunctions {

fun <A> done(a: A): TrampolineF<A> = Free.pure<Function0.F, A>(a)

fun <A> suspend(a: () -> TrampolineF<A>): TrampolineF<A> = defer(a)

fun <A> defer(a: () -> TrampolineF<A>): TrampolineF<A> = Free.defer(a)

fun <A> delay(a: () -> A): TrampolineF<A> = defer { done(a()) }
}
5 changes: 4 additions & 1 deletion kategory/src/main/kotlin/kategory/free/Free.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ sealed class Free<out S, out A> : FreeKind<S, A> {
fun <S, A> liftF(fa: HK<S, A>): Free<S, A> =
Suspend(fa)

fun <S, A> defer(value: () -> Free<S, A>): Free<S, A> =
pure<S, Unit>(Unit).flatMap { _ -> value() }

fun <S> functor(): FreeInstances<S> = object : FreeInstances<S> {}

fun <S> applicative(): FreeInstances<S> = object : FreeInstances<S> {}
Expand Down Expand Up @@ -81,4 +84,4 @@ fun <M, S, A> Free<S, A>.foldMap(f: FunctionK<S, M>, MM: Monad<M>): HK<M, A> =
}

fun <S, A> A.free(): Free<S, A> =
Free.pure<S, A>(this)
Free.pure<S, A>(this)
37 changes: 37 additions & 0 deletions kategory/src/test/kotlin/kategory/data/TrampolineTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kategory

import io.kotlintest.KTestJUnitRunner
import io.kotlintest.matchers.shouldBe
import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class TrampolineTest : UnitSpec() {

init {
"trampoline over 10000 should return false and not break the stack" {
odd(10000).foldMap(idFunction0Interpreter, Id).value() shouldBe false
}

"trampoline over 10001 should return true and not break the stack" {
odd(10001).foldMap(idFunction0Interpreter, Id).value() shouldBe true
}
}

fun odd(n: Int): TrampolineF<Boolean> {
return when (n) {
0 -> Trampoline.done(false)
else -> {
Trampoline.defer { even(n - 1) }
}
}
}

fun even(n: Int): TrampolineF<Boolean> {
return when (n) {
0 -> Trampoline.done(true)
else -> {
Trampoline.defer { odd(n - 1) }
}
}
}
}
9 changes: 8 additions & 1 deletion kategory/src/test/kotlin/kategory/free/Interpreters.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,11 @@ val idInterpreter: FunctionK<Ops.F, Id.F> = object : FunctionK<Ops.F, Id.F> {
is Ops.Value -> Id(op.a)
} as Id<A>
}
}
}

val idFunction0Interpreter: FunctionK<Function0.F, Id.F> = object : FunctionK<Function0.F, Id.F> {
override fun <A> invoke(fa: HK<Function0.F, A>): Id<A> {
val op = fa.ev()
return Id(op())
}
}