In section 1.1, where we began our discussion of models of evaluation, we noted that Scheme JavaScript is an applicative-order language, namely, that all the arguments to Scheme JavaScript procedures functions are evaluated when the procedure function is applied. In contrast, normal-order languages delay evaluation of procedure function arguments until the actual argument values are needed. Delaying evaluation of procedure function arguments until the last possible moment (e.g., until they are required by a primitive operation) is called lazy evaluation.[1] Consider the procedure function
Original | JavaScript |
(define (try a b) (if (= a 0) 1 b)) | function try_me(a, b) { return a === 0 ? 1 : b; } |
An example that exploits lazy evaluation is the definition declaration of a procedure function unless
Original | JavaScript |
(define (unless condition usual-value exceptional-value) (if condition exceptional-value usual-value)) | function unless(condition, usual_value, exceptional_value) { return condition ? exceptional_value : usual_value; } |
Original | JavaScript |
(unless (= b 0) (/ a b) (begin (display "exception: returning 0") 0)) | unless(is_null(xs), head(xs), display("error: xs should not be null")); |
If the body of a procedure function is entered before an argument has been evaluated we say that the procedure function is non-strict in that argument. If the argument is evaluated before the body of the procedure function is entered we say that the procedure function is strict in that argument.[2] In a purely applicative-order language, all procedures functions are strict in each argument. In a purely normal-order language, all compound procedures functions are non-strict in each argument, and primitive procedures functions may be either strict or non-strict. There are also languages (see exercise 4.40) that give programmers detailed control over the strictness of the procedures functions they define.
A striking example of a procedure function that can usefully be made non-strict is cons 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 cons pairs. pairs.
Original | JavaScript |
(define (factorial n) (unless (= n 1) (* n (factorial (- n 1))) 1)) | 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 procedures 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 procedures functions take their arguments by lazy evaluation.