[previous] [up] [next]     [index]
Next: Changing Compound Values Up: Intermezzo 7 The Final Syntax Previous: The Meaning of Advanced

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.

  tex2html_wrap73413    


[previous] [up] [next]     [index]
Next: Changing Compound Values Up: Intermezzo 7 The Final Syntax Previous: The Meaning of Advanced

PLT