There was a problem when proofreading this page.

Steele and Sussman


The Art of the Interpreter

{PROGN Wizardry} Page 35

We defined EVPROGN in the way shown in Figure 11 rather than in this "more obvious" way:

              (T (EVPROGN (CDR EXPS) ENV (EVAL (CAR EXPS) ENV)))))

for a technical reason: we would like the tail-recursive properties of the code being interpreted to be reflected in the interpretation process. We specifically want recursive calls as the last subform of a PROGN form to be tail-recursive if the PROGN form itself is in a tail-recursive situation. For example, we might write a loop such as:

        (COND ((= X 0) 'BLASTOFF)
              (T (PROGN (PRINT X)
                        (PRINTLOOP (- X 1))))))

We would like this loop to be iterative, but it can be iterative only if the recursive call to PRINTLOOP is tail-recursive. Our point is that if the "obvious" version of EVPROGN is used in the interpreter, then interpretation of PRINTLOOP will not be tail-recursive because of the "stacking up of EVPROGN frames" (the last call to EVAL from EVPROGN is not tail-recursive). This is unnecessary because EVPROGN does nothing with the last value but return it anyway.

By the way, the use of PROGN in a COND clause as shown above in PRINTLOOP is a very common situation, as is the use of a PROGN as the body of a procedure (cf. George's last experimental version of MAPCAR). As a convenience, most real LISP implementations define extended versions of COND and LAMBDA which implicitly treat clauses (resp. bodies) as PROGN forms (see Figure N8). This allows us to write such things as:

        (SLEEP 1)
        (COND ((= X 0) 'BLASTOFF)
              (T (PRINT X)
                 (PRINTLOOP (- X 1)))))