Section 38

Intermezzo 7: The Final Syntax and Semantics

With the introduction of set!-expressions and begin-expressions we have covered most of Scheme's kernel language. In DrScheme, this portion is called Advanced Student Scheme. Considering the complexity of set!, this is a good place to summarize our programming language in the spirit of intermezzo 1. Following the organization of that intermezzo, we discuss the vocabulary, the grammar, and the meaning of Advanced Student Scheme. The last subsection explains what kind of errors we may encounter in Advanced Student Scheme.

38.1  The Vocabulary of Advanced Scheme

The foundation of any language is its vocabulary. In Beginning Student Scheme, we distinguish four categories of words: variables, constants, primitive functions, and keywords. The classification ignores parentheses but we know that every compound phrase is surrounded by a pair of parentheses, and that every atomic phrase stands on its own.

Advanced Student Scheme respects this basic classification, though it contains four important new keywords: local, lambda, set!, and begin. The first two are important for organizing and abstracting programs; the last two are important for the computation of effects. Still, keywords per se have no meaning. They are road signs that tell us what is ahead so that we can orient ourselves. It is the grammar and the meaning of a language that explains the role of the keywords.

38.2  The Grammar of Advanced Scheme

Even though Scheme is a full-fledged language with as much power as any other programming language, its designers have kept its grammar simple. The grammar of Advanced Student Scheme, which is most of Scheme, is only a few lines longer than that of Beginning Student Scheme.

<def> =   (define (<var> <var> ...<var>) <exp>)
| (define <var> <exp>)
| (define-struct <var0> (<var-1> ... <var-n>))
<exp> =   <var>
| <con>
| <prm>
| (<exp> <exp> ...<exp>)
| (cond (<exp> <exp>) ...(<exp> <exp>))
| (cond (<exp> <exp>) ...(else <exp>))
| (local (<def> ...<def>) <exp>)
| (lambda (<var> ...<var>) <exp>)
| (set! <var> <exp>)
| (begin <exp> ...<exp>)
Figure 111:  Advanced Student Scheme: The core grammar

Figure 111 contains the essential grammar of the Advanced Student Scheme language.71 It extends Intermediate Student Scheme with three new forms of expressions: lambda-expressions, set!-expressions, and begin-expressions. The specification of local-expressions refers to the category of definitions, which refers back to the category of expressions. A lambda-expression consists of a sequence of variables, enclosed in parentheses, and an expression. Similarly, a set!-expression consists of a variable and an expression. Finally, a begin-expression is just a sequence of expressions prefixed with the keyword begin to distinguish it from an application.

Since functions are values now, the grammar of Advanced Student Scheme is also simpler than that of Beginning Student Scheme in one regard. Specifically, it merges the lines for primitive and function application into a single line. The new line specifies applications, which are now sequences of expressions enclosed in parentheses. Owing to the inclusion of primitive operations into the set of expressions,

(+ 1 2)

is still an expression. After all, + is now an expression, and so are 1 and 2. The application of defined functions works similarly:

(f 1 2)

The first expression is a variable, and the others are numbers. The application is thus a legal expression.

Unfortunately, a language grammar can only specify the large contours of what is a legal sentence construction. It cannot express restrictions that require some knowledge about the context of a phrase. Advanced Student Scheme requires a few such restrictions:

  1. In a lambda-expression no variable may occur twice in the parameter sequence.

  2. In a local-expression no definition may introduce the same variable as any other definition in the same sequence.

  3. A set!-expression must occur in the lexical scope of a define that introduces the set!-expression's left-hand side.

In addition, the old restriction applies that keywords cannot be used as variables.

Consider the following definition, which uses the new constructs:

(define switch
  (local ((define-struct hide (it))
	  (define state (make-hide 1)))
    (lambda ()
      (begin
	(set! state (make-hide (- 1 (hide-it state))))
	state))))

The definition introduces the variable switch. The right-hand side of the definition is a local-expression. It in turn defines the structure hide and the variable state, which stands for an instance of hide. The body of the local-expression is a lambda-expression, whose parameter sequence is empty. The function's body consists of a begin-expression with two expressions: a set!-expression and an expression that consists of just the variable state.

All expressions in our program satisfy the necessary restrictions. First, the local-expression introduces four names that are distinct: make-hide, hide?, hide-it, and state. Second, the parameter list of the lambda-expression is empty, so there is no possible conflict. Finally, the set!-expression's variable is the locally defined variable state, so it is legal, too.

Exercise 38.2.1.   Determine whether the following phrases are syntactically legal or illegal programs:

1. (define (f x) 
       (begin
	 (set! y x)
	 x))

2. (define (f x) 
       (begin
	 (set! f x)
	 x))

3. (local ((define-struct hide (it))
	     (define make-hide 10))
       (hide? 10))

4. (local ((define-struct loc (con))
	     (define loc 10))
       (loc? 10))

5. (define f
       (lambda (x y x)
	 (* x y z)))
     
     (define z 3.14)

Explain why a phrase is legal or illegal.    Solution

38.3  The Meaning of Advanced Scheme

When we first used Advanced Student Scheme, we did so because we wanted to deal with functions as ordinary values. The evaluation rules barely changed. We just agreed to allow expressions in the first position of an application and to deal with the names of functions as values.

The extension of the language with set!-expressions required another change to our rules. Now definitions that associate variables and values can change over the course of an evaluation. The informal rules we've used so far deal with changes to the definition of state variables, because they matter the most. But the rules are informal and imprecise, so a precise description of how the addition of set! changes the meaning of the language must be our primary concern.

Let's recall how we determine the meaning of a program. A program consists of two parts: a collection of definitions and an expression. The goal is to evaluate the expression, which means to determine the expression's value.72 In Beginning Student Scheme, the collection of values consists of all the constants plus lists. Only one list has a concise representation: the empty one. All other lists are written down as a series of constructed lists.

The evaluation of an expression consists of a series of steps. At each step we use the laws of arithmetic and algebra to simplify a subexpression. This yields another expression. We also say that we REWRITE the first expression into the second. If the second expression is a value, we are finished.

The introduction of set!-expressions into our programming language requires a few small adjustments and extensions to this process:

  1. Instead of rewriting just an expression, we must now rewrite definitions and expressions. More precisely, each step changes the expression and possibly the definition of a state variable. To make these effects as obvious as possible, each stage in an evaluation displays the definitions of state variables and the current expression.

  2. Furthermore, it is no longer possible to apply the laws of arithmetic and algebra whenever or wherever we want. Instead, we must determine the subexpression that we must evaluate if we wish to make progress. This rule still leaves us with choices. For example, when we rewrite an expression such as

    (+ (* 3 3) (* 4 4))
    

    we may choose to evaluate (* 3 3) and then (* 4 4) or vice versa. Fortunately, for such simple expressions, the choice doesn't affect the final outcome, so we don't have to supply a complete unambigous rule. In general, though, we rewrite subexpressions in a left-to-right and top-to-bottom order. At each stage in the evaluation, we best start by underlining the subexpression that must be evaluated next.

  3. Suppose the underlined subexpression is a set!-expression. By the restrictions on set!-expressions, we know that there is a define for the left-hand side of the subexpression. That is, we face the following situation:

      (define x aValue)
      ...
      ... (set! x anotherValue) ... 
    = (define x anotherValue)
      ...
      ... (void) ...
    

    The equation indicates that the program changes in two ways. First, the variable definition is modified. Second, the underlined set!-expression is replaced by (void), the invisible value.

  4. The next change concerns the replacement of variables in expressions with the value in their definition. Until now, we could replace a variable with its value wherever we thought it was convenient or necessary. Indeed, we just thought of the variable as a shorthand for the value. With set!-expressions in the language, this is no longer possible. After all, the evaluation of a set!-expression modifies the definition of a state variable, and if we replace a variable with its value at the wrong time, we get the wrong value.

    Suppoe that the underlined expression is a (state) variable. Then we know that we can't make any progress in our evaluation until we have replaced the variable with the current value in its definition. This suggests the following revised law for variable evaluation:

      (define x aValue)
      ...
      ... x ... 
    = (define x aValue)
      ...
      ... aValue ... 
    

    In short, substitute the value in a state variable definition for the state variable only when the value is needed for this particular occurrence of the state variable.

  5. Last, but not least, we also need a rule for begin-expressions. The simplest one says to drop the first subexpression if it is a value:

      (begin v exp-1 ... exp-n)
    = (begin exp-1 ... exp-n)
    

    That means we also need a rule for dropping begin completely:

      (begin exp)
    = exp
    

    In addition, we use a rule for dropping several values at once:

      (begin v-1 ... v-m exp-1 ... exp-n)
    = (begin exp-1 ... exp-n)
    

    But this is only a convenience.

Although the laws are more complicated than those of Beginning Student Scheme, they are still manageable.

Let's consider some examples. The first one demonstrates how the order of evaluation of subexpressions makes a difference:

  (define x 5)
  (+ (begin (set! x 11) x) x)

= (define x 11)
  (+ (begin (void) x) x)

= ...

= (define x 11)
  (+ 11 x)

= (define x 11)
  (+ 11 11)

The program consists of one definition and one addition, which is to be evaluated. One of the addition's arguments is a set!-expression that mutates x; the other is just x. By evaluating the subexpressions of the addition from left to right, the mutation takes place before we replace the second subexpression with its value. As a result, the outcome is 22. If we had evaluated the addition from right to left, the result would have been 16. To avoid such problems, we use the fixed ordering but give ourselves more freedom when no state variables are involved.

The second example illustrates how a set!-expression that occurs in a local-expression actually affects a top-level definition:

  (define (make-counter x0)
    (local ((define counter x0)
	    (define (increment)
	      (begin
		(set! counter (+ counter 1))
		counter)))
      increment))
  ((make-counter 0))

The program again consists of a single definition and an expression that is to be evaluated. The latter, however, is an application nested in an application. The inner application is underlined, because we must evaluate it to make progress. Here are the first few steps:

= (define (make-counter x0)
    (local ((define counter x0)
	    (define (increment)
	      (begin
		(set! counter (+ counter 1))
		counter)))
      increment))
  ((local ((define counter 0)
	   (define (increment)
	     (begin
	       (set! counter (+ counter 1))
	       counter)))
     increment))
= (define (make-counter x0)
    (local ((define counter x0)
	    (define (increment)
	      (begin
		(set! counter (+ counter 1))
		counter)))
      increment))
  (define counter1 0)
  (define (increment1)
    (begin
      (set! counter1 (+ counter1 1))
      counter1))
  (increment1)

The evaluation of the local-expression created additional top-level expressions. One of them introduces a state variable; the others define functions.

The second part of the evaluation determines what (increment1) accomplishes:

  (define counter1 0)
  (increment1)

= (define counter1 0)
  (begin
    (set! counter1 (+ counter1 1))
    counter1)

= (define counter1 0)
  (begin
    (set! counter1 (+ 0 1))
    counter1)

= (define counter1 0)
  (begin
    (set! counter1 1)
    counter1)

= (define counter1 1)
  (begin
    (void)
    counter1)

= (define counter1 1)
  1

During the evaluation, we replace counter1 with its value twice. First, the second step replaces counter1 with 0, its value at that point. Second, we substitute 1 for counter1 during the last step, which is its new value.

Exercise 38.3.1.   Underline the subexpression that must be evaluated next in the following expressions:

1. (define x 11)
     (begin
       (set! x (* x x))
       x)

2. (define x 11)
     (begin
       (set! x 
	 (cond
	   [(zero? 0) 22]
	   [else (/ 1 x)]))
       'done)

3. (define (run x)
       (run x))
     (run 10)

4. (define (f x) (* pi x x))
     (define a1 (f 10))
     (begin
       (set! a1 (- a1 (f 5)))
       'done)

5. (define (f x)
       (set! state (- 1 state)))
     (define state 1)
     (f (f (f)))

Explain why the expression must be evaluated.    Solution

Exercise 38.3.2.   Confirm that the underlined expressions must be evaluated next:

1. (define x 0)
     (define y 1)
     (begin 
       (set! x 3)
       (set! y 4)
       (+ (* x x) (* y y)))

2. (define x 0)
     (set! x 
       (cond
	 [(zero? x) 1]
	 [else 0]))

3. (define (f x)
       (cond
	 [(zero? x) 1]
	 [else 0]))
     (begin
       (set! f 11)
       f)

Rewrite the three programs to show the next state.    Solution

Exercise 38.3.3.   Evaluate the following programs:

1. (define x 0)
     (define (bump delta)
       (begin
	 (set! x (+ x delta))
	 x))
     (+ (bump 2) (bump 3))

2. (define x 10)
     (set! x (cond
	       [(zeor? x) 13]
	       [else (/ 1 x)]))

3. (define (make-box x)
       (local ((define contents x)
	       (define (new y)
		 (set! contents y))
	       (define (peek)
		 contents))
	 (list new peek)))
     
     (define B (make-box 55))
     (define C (make-box 'a))
     
     (begin
       ((first B) 33)
       ((second C)))

Underline for each step the subexpression that must be evaluated next. Show only those steps that involve a local-expression or a set!-expression.    Solution

In principle, we could work with the rules we just discussed. They cover the common cases, and they explain the behavior of the programs we have encountered. They do not explain, however, how an assignment works when the left-hand side refers to a defined function. Consider the following example, for which the rules still work:

  (define (f x) x)
  
  (begin 
    (set! f 10)
    f)

= (define f 10)
  
  (begin 
    (void)
    f)

Here f is a state variable. The set!-expression changes the definition so f stands for a number. The next step in an evaluation substitutes 10 for the occurrence of f.

Under ordinary circumstances, an assignment would replace a function definition with a different function definition. Take a look at this program:

  (define (f x) x)
  (define g f)
  (+ (begin (set! f (lambda (x) 22)) 5) (g 1))

The purpose of the underlined set!-expression is to modify the definition of f so that it becomes a function that always produces 22. But g stands for f initially. Since f is a the name of a function, we can think of (define g f) as a value definition. The problem is that our current rules change the definition of f and, by implication, the definition of g, because it stands for f:

= (define f (lambda (x) 22))
  (define g f)
  (+ (begin (void) 5) (g 1))

= (define f (lambda (x) 22))
  (define g f)
  (+ 5 (g 1))

= (define f (lambda (x) 22))
  (define g f)
  (+ 5 22)

Scheme, however, does not behave this way. A set!-expression can modify only one definition at a time. Here it modifies two: f's, which is intended, and g's, which happens through the indirection from g to f. In short, our rules do not explain the behavior of all programs with set!-expressions; we need better rules if we wish to understand Scheme fully.

<vdf> =   (define <var> <val>)
| (define-struct <var> (<var> ...<var>))
<val> = <con> | <lst> | <prm> | <fun> | <void>
<lst> = empty | (cons <val> <lst>)
<fun> = (lambda (<var> ...<var>) <exp>)
Figure 112:   Advanced Student Scheme: The values

The problem concerns the definitions of functions, which suggests that we take a second look at the representation of functions and function definitions. So far, we used the names of functions as values. As we have just seen, this choice may cause trouble in case the state variable is a function. The solution is to use a concrete representation of functions. Fortunately, we already have one in Scheme: lambda-expressions. Furthermore, we rewrite function definitions so that they turn into variable definitions with a lambda-expression on the right-hand side:

  (define (f x) x)

= (define f (lambda (x) x))

Even recursive definitions are evaluated in this manner:

  (define (g x) 
    (cond
      [(zero? x) 1]
      [else (g (sub1 x))]))

= (define g
    (lambda (x)
      (cond
	[(zero? x) 1]
	[else (g (sub1 x))])))

All other rules, including the rule for replacing variables with their values, remain the same.

Figure 112 specifies the set of values,73 as a subset of the set of expressions, and the set of value definitions, as a subset of the definitions. Using these definitions and the modified rules, we can take a second look at at the above example:

  (define (f x) x)
  (define g f)
  (+ (begin (set! f (lambda (x) 22)) 5) (g 1))

= (define f (lambda (x) x))
  (define g f)
  (+ (begin (set! f (lambda (x) 22)) 5) (g 1))

= (define f (lambda (x) x))
  (define g (lambda (x) x))
  (+ (begin (set! f (lambda (x) 22)) 5) (g 1))

= (define f (lambda (x) 22))
  (define g (lambda (x) x))
  (+ (begin (void) 5) (g 1))

= (define f (lambda (x) 22))
  (define g (lambda (x) x))
  (+ 5 (g 1))

= (define f (lambda (x) 22))
  (define g (lambda (x) x))
  (+ 5 1)

The key difference is that the definition of g directly associates the variable with a function representation, not just a name for a function.

The following program shows the effects of set!-expressions on functions with an extreme example:

(define (f x)
  (cond
    [(zero? x) 'done]
    [else (f (sub1 x))]))

(define g f)

(begin 
  (set! f (lambda (x) 'ouch))
  (symbol=? (g 1) 'ouch))

The function f is recursive on natural numbers and always produces 'done. Initially, g is defined to be f. The final begin-expression first modifies f and then uses g.

At first, we must rewrite the function definitions according to our modified rules:

= (define f
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  (define g f)
  (begin 
    (set! f (lambda (x) 'ouch))
    (symbol=? (g 1) 'ouch))

= (define f
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  (define g 
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  (begin 
    (set! f (lambda (x) 'ouch))
    (set! f (lambda (x) 'ouch))
    (symbol=? (g 1) 'ouch))

Rewriting the definition of f is straightforward. The major change concerns the definition of g. Instead of f it now contains a copy of the value for which f currently stands. This value contains a reference to f, but that is not unusual.

Next, the set!-expression modifies the definition of f:

  ...
= (define f
    (lambda (x) 
      'ouch))
  
  (define g 
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  
  (begin 
    (void)
    (symbol=? (g 1) 'ouch))

No other definition, however, is affected. In particular, the definition of g remains the same, though the f inside of g's value now refers to a new value. But we have seen this phenomenon before. The next two steps follow the basic rules of intermezzo 1:

  ...
= (define f
    (lambda (x) 
      'ouch))
  
  (define g 
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  
  (begin 
    (void)
    (symbol=? (f 0) 'ouch))

= (define f
    (lambda (x) 
      'ouch))
  
  (define g 
    (lambda (x)
      (cond
	[(zero? x) 'done]
	[else (f (sub1 x))])))
  
  (begin 
    (void)
    (symbol=? 'ouch 'ouch))

That is, the application of g eventually applies f to 0, which yields 'ouch. Hence the final result is true.

Exercise 38.3.4.   Validate that the following program evaluates to true:

(define (make-box x)
  (local ((define contents x)
	  (define (new y) (set! contents y))
	  (define (peek) contents))
    (list new peek)))

(define B (make-box 55))

(define C B)

(and 
  (begin 
    ((first B) 33)
    true)
  (= (second C) 33)
  (begin
    (set! B (make-box 44))
    (= (second C) 33))) 

Underline for each step the subexpression that must be evaluated next. Show only those steps that involve a local-expression or a set!-expression.    Solution

While we decided to rewrite function definitions so that their right-hand side are always lambda-expressions, we stuck with a function application rule that assumes function definitions in the style of Beginning Student Scheme. More concretely, if the definition context contains a definition such as

(define f (lambda (x y) (+ x y)))

and the expression is

(* (f 1 2) 5)

then the next step in the evaluation is

(* (+ 1 2) 5)

For other occasions, however, we just replace variables with the values in the respective definitions. If we followed that rule, we would rewrite

(* (f 1 2) 5)

to

(* ((lambda (x y) (+ x y))
    1 2)
   5)

At first glance, this exploration route ends here, because there are no laws for this application.

We can reconcile the two ideas with a new law, suggested by the last expression:

  ((lambda (x-1 ... x-n) exp)
   v-1 ... v-n)
= exp  with all  x-1 ... x-n  replaced by  v-1 ... v-n

The law serves as a replacement of the law of application from algebra in the study of the foundations of computing. By convention, this law is called the ßv (pronounced ``beta value'') axiom.

Beta and the Lambda Calculus: The orginal ß axiom was formulated by Alonzo Church in the late 1920s as follows:

  ((lambda (x) exp)
   exp-1)
= exp  with  x  replaced by  exp-1

It does not restrict the argument in a function application to be a value. The interest of Church and other logicians74 was to explore the principles of computation, what computation could achieve, and what it couldn't. They confirmed that the axiom and a small sublanguage of Scheme, namely,

<exp> =<var> | (lambda (<var>) <exp>) | (<exp> <exp>)
are enough to define all computable functions on a (simulation of) the natural numbers. Functions that cannot be formulated in this language are not computable.

The language and the ß axiom became known as the lambda (pronounced: ``lambda'') calculus. Gerald Sussman and Guy L. Steele Jr. later based Scheme on the lambda calculus. In the mid-1970s, Gordon Plotkin suggested the ßv axiom as a better method for understanding function applications in programming languages such as Scheme. 

38.4  Errors in Advanced Scheme

The extension of our language with functions as values introduces not only new powers for the programmer but also new possibilities for errors. Recall that there are three kinds of errors: syntax errors, run-time (or semantics) errors, and logical errors. Advanced Student Scheme turns a class of syntactic errors of Beginning Student Scheme into run-time errors. It also introduces a new form of logical error.

Consider the following program:

;; how-many-in-list : (listof X)  ->  N
;; to count how many items alist contains 
(define (how-many-in-list alist)
  (cond
    [empty? (alist)]
    [else (+ (how-many-in-list (rest alist)) 1)]))

In Beginning Student Scheme or Intermediate Student Scheme, DrScheme would have signaled a syntax error because alist is the parameter to a function but is also used as a function. Because functions are values in Advanced Student Scheme, DrScheme must now accept this function definition as syntactially correct. When the function is applied to empty or any other list value, however, DrScheme soon applies empty to no arguments, which is a run-time error. After all, lists are not functions. DrScheme signals immediately with an error message any attempt to apply a non-function and stops the evaluation.

The second form of error is logical. That is, a program that suffers from this form of error doesn't produce a syntax or a run-time error message. Instead, it produces wrong answers. Take a look at the following two definitions:

(define flip1
  (local ((define state 1))
    (lambda ()
      (begin
	(set! state (- 1 state))
	state))))
                
(define flip2
  (lambda ()
    (local ((define state 1))
      (begin
	(set! state (- 1 state))
	state))))
They differ in the order of two lines. One introduces a local definition whose body evaluates to a function. The other defines a function whose body contains a local-expression. According to our rules, the definition on the left rewrites to

(define state1 1)

(define flip1
  (lambda ()
    (begin
      (set! state1 (- 1 state1))
      state1)))
                
(define flip2
  (lambda ()
    (local ((define state 1))
      (begin
	(set! state (- 1 state))
	state))))

The one on the right already associates a name with a function.

Let us now see how the two functions have radically different behaviors. To do so, we evaluate the expressions

(and (= (flip1) 0)
     (= (flip1) 1)
     (= (flip1) 0))
                
(and (= (flip2) 0)
     (= (flip2) 1)
     (= (flip2) 0))
in the context of the respective definitions.

Here are the first four steps of the evaluation for the expression on the left-hand side:

  (define state1 1)
  (and (= (flip1) 0)
       (= (flip1) 1)
       (= (flip1) 0))

= (define state1 1)
  (and (= (begin
	    (set! state1 (- 1 state1))
	    state1)
	  0)
       (= (flip1) 1)
       (= (flip1) 0))

= (define state1 1)
  (and (= (begin
	    (set! state1 0)
	    state1)
	  0)
       (= (flip1) 1)
       (= (flip1) 0))

= (define state1 0)
  (and (= (begin
	    (void)
	    state1)
	  0)
       (= (flip1) 1)
       (= (flip1) 0))

= (define state1 0)
  (and (= 0 0)
       (= (flip1) 1)
       (= (flip1) 0))

The relevant definition context is the definition of state1, which we see changing from 1 to 0 during the third step. From this point, it is not difficult to validate that the expression produces true and that state1 ends up being 0.

Compare this with the first three steps in the evaluation of the right-hand expression:

  (and (= (flip2) 0)
       (= (flip2) 1)
       (= (flip2) 0))

= (and (= (local ((define state 1))
	    (begin
	      (set! state (- 1 state))
	      state))
	  0)
       (= (flip2) 1)
       (= (flip2) 0))

= (define state1 1)
  (and (= (begin
	    (set! state1 (- 1 state1))
	    state1)
	  0)
       (= (flip2) 1)
       (= (flip2) 0))

= (define state1 0)
  (and (= 0 0)
       (= (flip2) 1)
       (= (flip2) 0))

The only definition that matters here is the one for flip2. Superficially, the two evaluations are alike. But a closer look shows that the second one differs from the first in crucial way. It creates the definition for state1 while the first evaluation started with such a definition.

Here is the continuation of the second evaluation:

  ... 
= (define state1 0)
  (and true
       (= (local ((define state 1))
	    (begin
	      (set! state (- 1 state))
	      state))
	  1)
       (= (flip2) 0))

= (define state1 0)
  (define state2 1)
  (and true
       (= (begin
	    (set! state2 (- 1 state2))
	    state2)
	  1)
       (= (flip2) 0))

= (define state1 0)
  (define state2 0)
  (and true
       (= (begin
	    (void)
	    state2)
	  1)
       (= (flip2) 0))

= (define state1 0)
  (define state2 0)
  (and true
       (= 0 1)
       (= (flip2) 0))

It shows that flip2 creates a new definition every time it is applied and that it always produces 0. Contrary to its name, it does not flip the value of state upon every application. As a result, the evaluation ends now with two new top-level definitions and the value false.

The general moral is that a function defined in a local-expression is different from a function whose body contains a local-expression. The first ensures that some definitions are accessible only to a function. The definition exists once and only once for this function. In contrast, the second creates a new (top-level) definition for every evaluation of the function body. In the next part of the book, we exploit both ideas to create new kinds of programs.


71 The grammar misses and-expression and or-expression, and a few other short-cuts.

72 We also evaluate the right-hand side of definitions if they are not values, but we can safely ignore this minor issue here.

73 It lacks a specification of structural values, but they play no role in this discussion.

74 Logic is to computing what mathematics is to physics.