Pregunta

Actualmente estoy trabajando a través SICP usando Guile como mi idioma principal de los ejercicios. He encontrado un comportamiento extraño, mientras que la aplicación de los ejercicios en el capítulo 3.5. He reproducido este comportamiento usando Guile 1.4, 1.8.6 y Guile Guile 1.8.7 en una variedad de plataformas y estoy seguro de que no es específico de mi configuración.

Este código funciona bien (y calcula e):

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

El siguiente código debería dar un resultado idéntico:

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

Pero produce el mensaje de error:

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)

Así que cuando incrustado en una definición de procedimiento, el (definir y ...) no está disponible, mientras que fuera del procedimiento en el entorno global en el REPL que trabaja muy bien.

¿Qué estoy haciendo mal aquí? Puedo enviar el código auxiliar (es decir, las definiciones de integral, stream-mapa etc.) si es necesario, también. Con la excepción del código dependiente del sistema de contra-corriente, todos ellos están en el libro. Mi propia implementación de contra-corriente de Guile es el siguiente:

(define-macro (cons-stream a b)
  `(cons ,a (delay ,b)))
¿Fue útil?

Solución

La diferencia clave entre lo que sucede cuando se evalúa las definiciones de uno en uno en el REPL y cuando se coloca en el interior solve es que en el primer caso, se evalúan de forma secuencial, por lo tanto el y la (stream-map <some-function> y) expresión se refiere a ya está en ámbito de aplicación, mientras que con las definiciones internos o letrec, no está disponible todavía.

Curiosamente, MIT Esquema, que utilicé cuando va a través SICP, no tenía tal problema entonces y aún trata letrec y define internos de otra manera:

;; 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)

No estoy seguro sobre el original "Revisado informe sobre algorítmico Lenguaje Scheme" o R2RS, pero al menos desde el R3RS define internos se supone que es equivalente a letrec. Al parecer, esta peculiaridad del entorno del MIT influyó en el libro ... o quizás es al revés.

Otros consejos

No se puede tener DEFINEs internos que dependen el uno del otro; la especificación de lenguaje afirma explícitamente (R5RS 5.2.2):

  

... debe ser posible evaluar cada expresión de cada definición interna en un cuerpo sin asignar o se refieran al valor de cualquier variables se está definiendo.

Se puede pensar en esto como si el intérprete está recogiendo todos los define y evaluación de las mismas antes de su cuerpo en un orden aleatorio. Debido a que el orden es aleatorio, no puede haber ningún interdependencias si esperar que funcione.

Hay incluso una nota adjunta a la definición SOLVE (# 71) que dice que no va a trabajar sobre todos los regímenes.

Tienes que escribir el código para que una definición es muy claramente en el ámbito de la otra, al igual que con chalets anidados:

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

Siguiendo la idea en los comentarios (en referencia a la cita de R5RS 4.2.2) que han envuelto ahora las definiciones de "y" y "dy" en (lambda () ...)s:

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

Esto asegura que la parte que <init> de cada definición se puede evaluar sin hacer referencia a las variables definidas circulares ya que las definiciones son procedimientos en lugar de expresiones con otras variables, como en el caso original.

Ahora el código es ciertamente mucho más lento (ya que las funciones se envolverse de forma recursiva) y las necesidades de tamaño de pila para ser aumentado, pero las siguientes obras y produce el resultado correcto:

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

Con una modificación similar, código de ejemplo de Michal funciona tan pronto como uno define los procedimientos en lugar de variables:

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

trabaja en Guile 1.8.6.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top