Lazy Evaluation

We're used to eager evaluation of expressions:

code43

Arguments to function and constructor applications are evaluated to values (base types, datatypes, functions, etc.) before the function is entered or the construction is made.

Evaluating arguments can be expensive (perhaps infinitely so), and sometimes this effort is wasted:

code46

Under lazy evaluation, expressions are not evaluated until they are needed, i.e., used as arguments to a primitive function or subjected to a case pattern match. Sometimes they are not evaluated at all. On the other hand, expressions should not be evaluated more than once either:

code51

Most pure functional languages, such as Miranda and Haskell, use lazy evaluation.

Conditionals are Lazy

Even eager languages have one form of lazy operator: the if-then-else or case construct:

code57

It's essential that only one arm of the conditional be executed.

In a lazy language, we can actually write conditionals as functions:

code59

Order of Evaluation

Note that the order of evaluation of expressions in lazy programs is very hard to predict (and can depend on runtime values).

For this reason, side-effects don't fit in well with lazy languages, since the order of their execution is important. Hence only purely functional languages use laziness.

code63

IO is particularly problematic.

Example: Comparing Tree Fringes

Consider the problem of comparing the fringes (leaves in left-to-right order) of two binary trees, not necessarily having the same shape.

code67

Here a and b have the same fringes, as do c and d.

samefringe is quite tricky to write if we operate directly on the trees (try it!), but here's a simple solution (in eager ML)

code74

The only problem is that samefringe always flattens both trees into lists before comparing them, even if the very leftmost leaves are unequal. This could be quite expensive if the trees are large. But if we run the same program assuming lazy evaluation, only the heads of the lists need to be computed.

Lazy Fringes

code79

Infinite Data Structures

Consider the following definition:

code82

This doesn't represent a finite object, so may appear not to make sense. But suppose it happens (under lazy evaluation) that we never need to fully evaluate inftree, but only a finite part of it.

code86

Then the definition of inftree makes perfect sense.

Using infinite data structures with lazy evaluation is particularly useful for building modular solutions to generate-and-test problems. We write one function to generate a (potentially) infinite tree of candidate solutions, and another to test the validity of a solution; just composing the functions gives an efficient program that automatically prunes useless branches of the search space.

Sieve of Eratosthenes

A (potentially) infinite list can be viewed as a stream: an unbounded sequence of values provided on demand.

code92

Using streams often produces neat solutions to problems.

code94

Encoding lazy evaluation in eager languages

We can get the effect of laziness in eager languages by making use of explicit suspensions. The idea is to represent a lazy expresion of type 'a by an eager expression of type unit -> 'a. When a value is needed, we must obtain it by forcing the expression, i.e., applying it to ():unit.

code102

We can be a little neater by wrapping things into a datatype:

code104

Actually, to avoid repeated evaluation of suspensions we need a fancier encoding:

code106



Andrew P. Tolmach
Tue Jun 3 15:28:56 PDT 1997