Skip to content

Eager Evaluation VS Lazy Evaluation

Hu JiaJun edited this page Nov 29, 2020 · 1 revision

What is Lazy Evaluation

  • Computation is postponed, and carried out only upon demand.
  • Popular explanation: Do not calculate and return a value immediately for the given expression, but only calculate when the value needs to be used.

What is Eager Evaluation

  • Computation is immediately carried out when the expression is encountered.
  • The opposite of Lazy Evaluation is Eager Evaluation, which is a evaluation method found everywhere, such as:int x = 1;, String name = getUserName();, etc.
  • The above expression is evaluated immediately after binding the variable to get the result of the evaluation.

Simple Demo

Template

/**
 * Stream can be replaced with anyother classes which can perform Lazy Evaluation, such as Optional.
 */
Stream.LazyEvaluation.LazyEvaluation.LazyEvaluation...LazyEvaluation.EagerEvaluation

Example

public class Person {

    private String name;
    private int score;

    Person(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }
}

public static void eagerAndLazyEvaluationExample() {
    List<Person> persons = List.of(new Person("Oliver", 88), new Person("George", 52), new Person("Harry", 35),
            new Person("Jack", 78), new Person("Jacob", 99), new Person("Noah", 48));

    // Lazy Evaluation
    Stream<Person> personStream = persons.stream().filter(person -> person.getScore() >= 50);
    // Eager Evaluation
    // Take out the value from the stream, and call the evaluation immediately 
    System.out.println(personStream.count());
    // The stream can only be used once, otherwise runtime error will be thrown: 
    // "stream has already been operated upon or closed"
    // System.out.println(personStream.count());
}

/* Output */
4
  • The whole process is divided into two steps: filtering and counting.
  • To determine whether an operation is Eager Evaluation or Lazy Evaluation, only need to pay attention to its return type. If the return type is Stream, it is Lazy Evaluation, like filtering step; otherwise it is Eager Evaluation, like counting step. The ideal way of these operations is to form a Lazy Evaluation chain, and finally use an Eager Evaluation operation to return the desired result.
  • The Stream object returned by the Lazy Evaluation method is not a new collection, but a formula for creating a new collection. Stream itself does not do any iterative operations. Only when the Eager Evaluation method is called, the real iteration will begin.

Eager Evaluation VS Lazy Evaluation

The advantage of Eager Evaluation

  • Save memory space and increase execution speed.
int x = 5 + 3 * (1 + 5 * 5);
System.out.println(x);
System.out.println(x + 2);
  • When the first line of code int x = 5 + 3 * (1 + 5 * 5); is executed, x is assigned and stored as 83. The space occupied by the expression 5 + 3 * (1 + 5 * 5) can be released immediately to save memory space.
  • The next two lines of code need to use the value of x when executing. In this case, x can directly use the result 83 of the previous line of code execution instead of the expression 5 + 3 * (1 + 5 * 5) that needs to be calculated to reduce one calculation process and improve execution efficiency.

The advantage of Lazy Evaluation

  • Avoids unnecessary computation
  • Amortizes time complexity (when used with memoization (aka caching))
  • Allows for infinite data structures

Evaluation Strategy & Thunk

The Thunk function was born in the 1960s. At that time, programming languages had just started and computer scientists were still studying how to write compilers better. The focus of the debate is the "evaluation strategy", that is, when the parameters of a function should be evaluated.

public static int esExample(int x) {
    return x * 2;
}

int x = 1;
esExample(x + 5);

The above code defines the function esExample() first, then passes the expression x + 5 into it. The question is, when should this expression be evaluated?

Call By Value

Definition: Evaluate the result of x + 5 (equal to 6) before entering the function body, then pass the result into the function esExample(). C and Java language uses this strategy.

esExample(x + 5);
// When calling by value, it is equivalent to
esExample(6);

Call By Name

Definition: Pass the expression x + 5 into the function body directly, and evaluate it only when it is used. Hskell language uses this strategy.

esExample(x + 5);
// When calling by name, it is equivalent to
(x + 5) * 2;

Call By Value VS Call By Name

Call by value is relatively simple, but if parameter is not actually used after evaluating, it may cause performance loss.

public static int esExample2(int x, int y) {
    return y;
}

int x = 1;
esExample2(3 * x * x - 2 * x - 1, x);

In the above code, the first parameter of the function esExample2() is a complex expression, but the function body never use it at all. Evaluating this parameter is actually unnecessary. Therefore, some computer scientists tend to Call By Name, that is, evaluation only during execution.

Thunk Function

Definition: The implementation of Call By Name of the compiler is generally to put the parameters in a temporary function, and then pass the temporary function into the function body. This temporary function is called the Thunk Function.

/**
 * Call By Value
 */
public static int esExample(int x) {
    return x * 2;
}

int x = 1;
esExample(x + 5);

// It is equivalent to

/**
 * Call By Name
 * x + 5 will not be evaluated before the thunk.get() is called.
 */
public static int esExample3(Supplier<Integer> thunk) {
    System.out.println("Before calling thunk.get()");
    int result = thunk.get();
    System.out.println("After calling thunk.get()");
    System.out.println("After evaluating x + 5: " + result);
    return result * 2;
}

int x = 1;
Supplier<Integer> thunk = new Supplier<>() {
    @Override
    public Integer get() {
        System.out.println("Before evaluating x + 5: " + x);
        return x + 5;
    }
};
System.out.println(esExample4(thunk));

/* Output */
Before calling thunk.get()
Before evaluating x + 5: 1
After calling thunk.get()
After evaluating x + 5: 6
12
Clone this wiki locally