[previous] [up] [next]     [index]
Next: Errors in Advanced Scheme Up: Intermezzo 7 The Final Syntax Previous: The Grammar of Advanced

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.[footnote] 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)
      ... 
      ...  tex2html_wrap_inline73370  ... 
    = (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)
      ... 
      ...  tex2html_wrap_inline73372  ... 
    = (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.


external

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

  (define x 5)
  (+ (begin  tex2html_wrap_inline73374  x) x)

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

= ...

= (define x 11) (+ 11 tex2html_wrap_inline73372 )

= (define x 11) tex2html_wrap_inline73380

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)) 
  ( tex2html_wrap_inline73382 ) 
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)
   tex2html_wrap_inline73384 

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

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

= (define counter1 0) (begin tex2html_wrap_inline73390 counter1)

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

= (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.


Exercises

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 
          tex2html_wrap_inline73406  
         (set! y 4) 
         (+ (* x x) (* y y)))

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

3. (define (f x) (cond [(zero? x) 1] [else 0])) (begin tex2html_wrap_inline73414 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 tex2html_wrap_inline73422 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  tex2html_wrap_inline73424  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) 
  (+  tex2html_wrap_inline73426  (g 1))

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

= (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.


tabular48035

Figure: 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 [cross-reference] specifies the set of values,[footnote] 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:

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

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

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

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

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

= (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  tex2html_wrap_inline73464 ) 
  (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 tex2html_wrap_inline73466 (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=? tex2html_wrap_inline73428 '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.


Exercises

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  tex2html_wrap_inline73470  x-1 ... x-n  tex2html_wrap_inline73472  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 tex2html_wrap_inline73474 (pronounced ``beta value'') axiom.

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

  ((lambda (x) exp)
   exp-1) 
= exp  tex2html_wrap_inline73482  x  tex2html_wrap_inline73472  exp-1 
It does not restrict the argument in a function application to be a value. The interest of Church and other logicians[footnote] 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,

tabular41258

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 tex2html_wrap_inline73478 axiom became known as the tex2html_wrap_inline73476 (pronounced: ``lambda'') calculus. Gerald Sussman and Guy L. Steele Jr. later based Scheme on the tex2html_wrap_inline73476 calculus. In the mid-1970s, Gordon Plotkin suggested the tex2html_wrap_inline73474 axiom as a better method for understanding function applications in programming languages such as Scheme. 



[previous] [up] [next]     [index]
Next: Errors in Advanced Scheme Up: Intermezzo 7 The Final Syntax Previous: The Grammar of Advanced

PLT