sicp-ex-4.27



<< Previous exercise (4.26) | Index | Next exercise (4.28) >>


xdavidliu

The strange behavior of the variable count happens not because of the first expression in the body of id (e.g. "identity"), but rather the *second*. To see what I mean, let's first examine the example from the exercise. In order to provide more clarity, I will call eval and actual-value directly as opposed to using the driver-loop REPL provided in the book.

 (let ((env the-global-environment)) 
   (eval '(define count 0) env) 
   (eval '(define w (id (id 10))) env) 
   (assert (= 1 (eval 'count env))) 
   (assert (= 10 (actual-value 'w env))) 
   (assert (= 2 (eval 'count env)))) 

Here, the 10 and 2 are trivial and require no explanation. The 1 is most interesting: it is because when we eval the expression (id (id 10)) in env when we are first defining w, the procedure list-of-delayed-args has x in the body of id bound to the result of (delay-it '(id 10) env), a thunk. Since (eval-definition exp env) calls eval, not actual-value, on (definition-value exp), it is the thunk that is bound to w, not the number 10. That thunk is not forced until we call actual-value on w, and hence before doing that, count has been incremented only once.

Note we have used eval for the assert lines involving count, since count is always bound to a number, but I have used actual-value for the assert involving w, since at that moment w is bound to a thunk and must be forced. If we had tried to call eval on w immediately after defining it, eval would return the thunk object, and attempting to print it may result in an infinite recursion loop since the thunk object contains env, which is a list that may contain cycles.

Interestingly, if we slightly modify the definition of id, we actually get different behavior:

 (eval '(define (id2 x) 
          (set! count (plus count 1)) 
          (+ 0 x)) ; note (+ 0 x) as opposed to x 
       the-global-environment) 
  
 (let () 
   (eval '(define count 0) the-global-environment) 
   (eval '(define w (id2 (id2 10))) the-global-environment) 
   (assert (= 2 (eval 'count the-global-environment))) ; note 2 not 1! 
   (assert (= 10 (actual-value 'w the-global-environment))) 
   (assert (= 2 (eval 'count the-global-environment)))) 

Here, the expression (+ 0 x), unlike the expression x, immediately gets forced, even though x is bound to a thunk, since + is a primitive procedure. Hence, the strange behavior of count is *not* due to the set! part of id, but rather the x part!


canardivore

  
  
  
 (define w (id (id 10))) 
  
 ;;; L-Eval input: 
 count 
 ;;; L-Eval value: 
 0 
 Defining w doesn't evaluate it. 
 ;;; L-Eval input: 
 w 
 ;;; L-Eval value: 
 10 
 now that w is in the prompt, w is forced to evaluate, evaluating both ids. so now w = 10, count = 2. 
 ;;; L-Eval input: 
 count 
 ;;;; L-Eval value: 
 2 

uuu

I got different result, which is very interesting

  
 ;;; L-Eval input: 
 (define count 0) 
 ;;; L-Eval value: 
 ok 
 ;;; L-Eval input: 
 (define (id x) (set! count (+ count 1)) x) 
 ;;; L-Eval value: 
 ok 
 ;;; L-Eval input: 
 (define w (id (id 10))) 
 ;;; L-Eval value: 
 ok 
 ;;; L-Eval input: 
 count 
 ;;; L-Eval value: 
 1 
 **It's not zero here** 
 ;;; L-Eval input: 
 w 
 ;;; L-Eval value: 
 10 
 ;;; L-Eval input: 
 count 
 ;;; L-Eval value: 
 2 
 **changed after "w"** 
 ;;; L-Eval input: 
 w 
 ;;; L-Eval value: 
 10 
 ;;; L-Eval input: 
 count 
 ;;; L-Eval value: 
 3 
 **changed after "w", again!** 
 ;;; L-Eval input: 
 w 
 ;;; L-Eval value: 
 10 
 ;;; L-Eval input: 
 count 
 ;;; L-Eval value: 
 4 
 **changed after "w", one more time!** 
 ;;; L-Eval input: 
  

poly

Actually, the value of count will never change no matter how many times you evaluate 'w' (at least once), if you have a mermoized thunk.