Control over evaluation
Using Eval
you can control evaluation of a value or a computation that produces a value.
This is useful to delay potentially costly computations, and to prevent start
overflows by carefully choosing when computations should take place.
You construct Eval
instance by providing a function that computes a value, together with an evaluation strategy.
There are three basic strategies:
Eval.now
evaluates the function immediately.Eval.later
waits until the first time the value is requested. Once computed, the result is saved, so subsequent calls return immediately.Eval.always
evaluates the function every time we need its value. If you ask for the value more than once, the function is executed again.
We say that Now
is an eager evaluation strategy, and Later
and Always
are lazy evaluation strategies.
One of the main use cases for Eval
is stack safety, that is, preventing stack overflows for operations with deep recursion.
For example, here is a (overly complicated) way to compute whether a number is even or odd, by jumping between even
and odd
until we reach 0
.
This approach would lead to stack overflow for big numbers, but we can prevent this using Eval
. Using Eval.always { n == 0}
we indicate that we want the evaluation to be performed when we need the answer; using later
or always
does not make a big difference here, since we only evaluate once per n
in any case. We indicate the next operation by using flatMap
.
import arrow.core.Eval
fun even(n: Int): Eval<Boolean> =
Eval.always { n == 0 }.flatMap {
if(it == true) Eval.now(true)
else odd(n - 1)
}
fun odd(n: Int): Eval<Boolean> =
Eval.always { n == 0 }.flatMap {
if(it == true) Eval.now(false)
else even(n - 1)
}
fun main() {
println(odd(100000).value())
}
One difference between Eval
and DeepRecursiveFunction
is that with Eval
we can call functions directly instead of using callRecursive
. However, the latter is more performant in general, so we advise using it unless you require the additional control provided by Eval
.
You should not use when
with Eval
instances. Rather, use map
and flatMap
to chain computations and value
to get the result when needed.
You should not create Eval
instances that call value
on other Eval
instances. This defeats the barriers in place against stack overflows.