Skip to content
John McClean edited this page Sep 16, 2018 · 2 revisions

Eval : lazy, stack-safe evaluation

Eval allows us to chain operations that are evaluated lazily (or lazily with caching / memoization). Eval is a stack-safe control type, tail-recursive calls to Eval within map / flatMap operations are trampolined. Eval can be used to safely implement recursive algorithms in Java.

Creating an Eval

now

now creates an Eval eagerly, but all chained operations are deferred / lazy

Eval<Integer> eager = Eval.now(10);

Eval<Integer> lazy = eager.map(i->i*2);

later

later creates an Eval lazily, that will be evaluated only once no matter how many times get is called

Eval<Integer> lazy = Eval.later(this::expensiveMethod);

lazy.get(); //triggers expensiveMethod
lazy.get(); //returns cached value

Subsequent map / flatMap calls are also evaluated only once (and lazily)

always

always creates an Eval lazily, but is evaluated repeatedly with get() is called

Eval<Integer> lazy = Eval.always(this::expensiveMethod);

lazy.get(); //triggers expensiveMethod
lazy.get(); //triggers expensiveMethod

Eval

Stack safety

The map / flatMap operators in Eval convert recursive tail calls to iteration.

public void runFib(){
    System.out.println(fibonacci(Eval.now(tuple(100_000,1l,0l))));
}
    
public Eval<Long> fibonacci(Eval<Tuple3<Integer,Long,Long>> fib)  {
    return fib.flatMap(t->t.v1 ==  0 ? Eval.now(t.v3) : fibonacci(Eval.now(tuple(t.v1-1,t.v2+t.v3,t.v2))));
}

The above code can be rewritten to use Suppliers in the method signature instead

public void runFib(){
    System.out.println(fibonacci(Eval.now(tuple(100_000,1l,0l)).get()));
}

public Supplier<Long> fibonacci(Supplier<Tuple3<Integer,Long,Long>> fib)  {
    return Eval.eval(fib).flatMap(t->t.v1 ==  0 ? Eval.now(t.v3) : fibonacci(Eval.now(tuple(t.v1-1,t.v2+t.v3,t.v2))));
}

Concurrency

The zip method on Eval can be used to Evaluate multiple lazy code blocks concurrently, and only accepts another Eval, Trampoline or Supplier as a parameter.

public void interleave(){

        Eval<Integer> algorithm1 = loop(50000,Eval.now(5));
        Eval<Integer> algorithm2 = loop2(50000,Eval.now(5));

        //interleaved execution via Zip!
        Tuple2<Integer, Integer> result = algorithm1.zip(algorithm2,Tuple::tuple).get();

        System.out.println(result);
}

Eval<Integer> loop2(int times,Eval<Integer> sum){
        System.out.println("Loop-B " + times + " : " + sum);
        if(times==0)
            return sum;
        else
            return sum.flatMap(s->loop2(times-1,Eval.now(s+times)));
}

Eval<Integer> loop(int times,Eval<Integer> sum){
        System.out.println("Loop-A " + times + " : " + sum);
        if(times==0)
            return sum;
        else
            return sum.flatMap(s->loop(times-1,Eval.now(s+times)));
}
Producing output showing executing of interleaved lazy calls
Loop-B 15746 : Always[1126048874]
Loop-A 15745 : Always[1126064620]
Loop-B 15745 : Always[1126064620]
Loop-A 15744 : Always[1126080365]
Loop-B 15744 : Always[1126080365]
Loop-A 15743 : Always[1126096109]
Loop-B 15743 : Always[1126096109]
Loop-A 15742 : Always[1126111852]
Loop-B 15742 : Always[1126111852]
Loop-A 15741 : Always[1126127594]
Clone this wiki locally