Circular definition problem in schematic

I am currently working through SICP using Guile as my primary exercise language. I found strange behavior when doing the exercises in chapter 3.5. I have reproduced this behavior using Guile 1.4, Guile 1.8.6, and Guile 1.8.7 on various platforms, and I'm pretty sure this is not the case for my setup.

This code works fine (and calculates e):

  (define y (integral (delay dy) 1 0.001))
  (define dy (stream-map (lambda (x) x) y))
  (stream-ref y 1000)

      

The following code should give an identical result:

  (define (solve f y0 dt)
    (define y (integral (delay dy) y0 dt))
    (define dy (stream-map f y))
    y)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

      

But this gives an error message:

standard input:7:14: While evaluating arguments to stream-map in expression (stream-map f y):
standard input:7:14: Unbound variable:
y ABORT: (unbound-variable)

      

So when embedded in a procedure definition, (define y ...) does not work, whereas outside of a procedure in the global environment on the REPL it works fine.

What am I doing wrong here? I can add some supporting code if needed (i.e. Integral definitions, flow maps, etc.). Except for the system code for the cons thread, they are all in the book. My own cons-stream implementation for Guile looks like this:

(define-macro (cons-stream a b)
  `(cons ,a (delay ,b)))

      

+2


a source to share


3 answers


The key difference between what happens when you evaluate definitions one by one in the REPL and when you place them inside solve

is that in the first case they are evaluated sequentially, so the y

expression (stream-map <some-function> y)

refers to already in scope, whereas with internal definitions or letrec

it is not yet available.

Oddly enough, the MIT Scheme that I used when going through SICP did not have such a problem then and still refers to letrec

, and the internal one defines differently:



;; this is an error
(letrec ((xs '(1 2 3)) (ys (map (lambda (x) (+ x 1)) xs))) ys)

;; this is still an error (and is treated as such by Guile),
;; yet evaluates to (2 3 4) in MIT Scheme
(let () (define xs '(1 2 3)) (define ys (map (lambda (x) (+ x 1)) xs)) ys)

      

I'm not sure about the original "Algorithmic Language Scheme Revised Report" or R2RS, but at least from R3RS internally it should have been equivalent letrec

. Apparently this feature of the MIT environment influenced the book ... or perhaps vice versa.

+1


a source


You cannot have internal DEFINEs that depend on each other; the language specification explicitly states this (R5RS 5.2.2):

... it should be possible to evaluate every expression of every internal definition in the body without assigning or referencing the value of any defined variable.

You can think of it as if the interpreter is collecting all the DEFINITIONS and evaluating them down to the body in a random order. Since the order is random, there cannot be any interdependence if you expect it to work.



There's even a footnote attached to the definition of SOLVE (# 71) which says it won't work on all circuits.

You have to write code to make one definition very clear in the scope of another, for example with nested LETs:

(define (solve f y0 dt)
  (let ((y (integral (delay dy) y0 dt)))
    (let ((dy (stream-map fy)))
      y)))
+2


a source


Following the idea in the comments (referencing a quote from R5RS 4.2.2), I've now wrapped the definitions of " y

" and " dy

" in (lambda () ...)

s:

  (define (solve f y0 dt)
    (define (y) (integral (delay (dy)) y0 dt))
    (define (dy) (stream-map f (y)))
    (y))

      

This ensures that a portion of <init>

each definition can be evaluated without referencing the circularly defined variables, since definitions are procedures and not expressions with other variables as in the original case.

Now the code is of course much slower (since functions will wrap recursively) and the stack size has to be increased, but the following works and gives the correct result:

  (debug-set! stack 2000000)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

      


With a similar modification, Michał's example code works as soon as you define procedures, not variables:

  (let ()
    (define (xs) '(1 2 3))
    (define (ys) (map (lambda (x) (+ x 1)) (xs)))
    (ys))

      

works with Guile 1.8.6.

0


a source







All Articles