We're used to eager evaluation of expressions:
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:
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:
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:
It's essential that only one arm of the conditional be executed.
In a lazy language, we can actually write conditionals as functions:
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.
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.
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)
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.
Infinite Data Structures
Consider the following definition:
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.
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.
Using streams often produces neat solutions to problems.
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.
We can be a little neater by wrapping things into a datatype:
Actually, to avoid repeated evaluation of suspensions we need a fancier encoding: