Closed AcartN closed 1 year ago
These two helpers would make it easier to create recursive memoized functions:
fun <P, R> memoizedFun(function: (params: P, recurse: (P) -> R) -> R): (P) -> R {
val memoizedParams = mutableMapOf<P, R>()
fun memoizedFun(params: P): R = memoizedParams
.getOrPut(params) { function(params, ::memoizedFun) }
return ::memoizedFun
}
val memoizedFibonacci: (Int) -> Int = memoizedFun { n, recurse ->
when (n) {
0 -> 0.also { println("Should be printed only once if memoized") }
1 -> 1
else -> recurse(n - 1) + recurse(n - 2)
}
}
class MemoizedDeepRecursiveFunction<P, R>(
private val block: suspend DeepRecursiveScope<P, R>.(P) -> R
) {
private val memoizedParams = mutableMapOf<P, R>()
private val deepRecursiveFunction = DeepRecursiveFunction { p ->
memoizedParams.getOrPut(p) { block(p) }
}
operator fun invoke(p: P) = deepRecursiveFunction(p)
}
val memoizedDeepRecursiveFibonacci: MemoizedDeepRecursiveFunction<Int, Int> =
MemoizedDeepRecursiveFunction { n ->
when (n) {
0 -> 0.also { println("Should be printed only once if memoized") }
1 -> 1
else -> callRecursive(n - 1) + callRecursive(n - 2)
}
}
Maybe they could be included in the library.
I've created this PR based on the conversation above. It turns out that we don't even need a separate MemoizedDeepRecursiveFunction
class if we play well with the scopes.
I've opened this PR https://github.com/arrow-kt/arrow-website/pull/215 to update the docs to point to the new MemoizedDeepRecursiveFunction
. Feel free to have a look, and thanks for moving this discussion forward 😄
First, thanks for the arrow library.
This issue originated from a conversation in the #arrow channel on the kotlinlang Slack. Here is the link to the thread: Slack Thread
I was quite surprised when reading the documentation and came across the memoize function in arrow. In the documentation, it's illustrated with the Fibonacci sequence, which requires recursive calls. According to the documentation, you can take a simple
DeepRecursiveFunction
, apply.memoize
, and obtain a function in aval
that takes care of memoizing all the calls. No need to modify the original function. The promise was so enticing that I decided to test it out myself.However, I encountered two issues:
Here are the details for each problem:
val fibonacciWorker = DeepRecursiveFunction<Int, Int> { n -> when (n) { 0 -> 0 1 -> 1 else -> callRecursive(n - 1) + callRecursive(n - 2) } }
val fibonacciMemoized = ::fibonacciWorker.memoize()
fun fibonacci(n: Int): Int { require(n >= 0) return fibonacciMemoized(n) }
e: file:///Users/.../fibo.kt:18:12 Type mismatch: inferred type is DeepRecursiveFunction<Int, Int> but Int was expected
import arrow.core.memoize
val fibonacciWorker = DeepRecursiveFunction<Int, Int> { n -> print(".") // I added this print statement in order to visualize all the recursive calls. when (n) { 0 -> 0 1 -> 1 else -> callRecursive(n - 1) + callRecursive(n - 2) } }
fun fib(n: Int) = fibonacciWorker(n) // wrap the DeepRecursiveFunction in a regular one
val fibonacciMemoized = ::fib.memoize()
fun fibonacci(n: Int): Int { require(n >= 0) return fibonacciMemoized(n) }
// Show that the memoize function actually memoize the call to hat function but not all the recursive calls fun main() { println(fibonacci(4)) // .........3 println(fibonacci(4)) // 3 => has been memoized println(fibonacci(3)) // .....2 => recursive calls have not been memoized }