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.
Control over evaluation lives in the arrow-eval library.
You construct Eval instance by providing a function that computes a value, together with an evaluation strategy.
There are four strategies:
Eval.nowevaluates the function immediately.Eval.laterwaits until the first time the value is requested. Once computed, the result is saved, so subsequent calls return immediately. Note that evaluation may be performed several times if two or more threads ask for the value concurrently, and the result is not yet saved.Eval.atMostOnceworks aslazy, but ensures that evaluation is performed at most once. As a result, getting a value from such anEvalinstance may block (or suspend) if another thread is already evaluating it.Eval.alwaysevaluates 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, atMostOnce, 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.