In section 1.1, where we began our discussion of models of evaluation, we noted that JavaScript is an applicative-order language, namely, that all the arguments to JavaScript functions are evaluated when the function is applied. In contrast, normal-order languages delay evaluation of function arguments until the actual argument values are needed. Delaying evaluation of function arguments until the last possible moment (e.g., until they are required by a primitive operation) is called lazy evaluation.[1] Consider the function
function try_me(a, b) {
return a === 0 ? 1 : b;
}
An example that exploits lazy evaluation is the declaration of a function unless
function unless(condition, usual_value, exceptional_value) {
return condition ? exceptional_value : usual_value;
}
unless(is_null(xs), head(xs), display("error: xs should not be null"));
If the body of a function is entered before an argument has been evaluated we say that the function is non-strict in that argument. If the argument is evaluated before the body of the function is entered we say that the function is strict in that argument.[2] In a purely applicative-order language, all functions are strict in each argument. In a purely normal-order language, all compound functions are non-strict in each argument, and primitive functions may be either strict or non-strict. There are also languages (see exercise 4.29) that give programmers detailed control over the strictness of the functions they define.
A striking example of a function that can usefully be made non-strict is pair (or, in general, almost any constructor for data structures). One can do useful computation, combining elements to form data structures and operating on the resulting data structures, even if the values of the elements are not known. It makes perfect sense, for instance, to compute the length of a list without knowing the values of the individual elements in the list. We will exploit this idea in section 4.2.3 to implement the streams of chapter 3 as lists formed of non-strict pairs.
function factorial(n) {
return unless(n === 1,
n * factorial(n - 1),
1);
}
lazyterminology and the
normal-orderterminology is somewhat fuzzy. Generally,
lazyrefers to the mechanisms of particular evaluators, while
normal-orderrefers to the semantics of languages, independent of any particular evaluation strategy. But this is not a hard-and-fast distinction, and the two terminologies are often used interchangeably.
strictversus
non-strictterminology means essentially the same as
applicative-orderversus
normal-order,except that it refers to individual functions and arguments rather than to the language as a whole. At a conference on programming languages you might hear someone say,
The normal-order language Hassle has certain strict primitives. Other functions take their arguments by lazy evaluation.