¿Cuándo un programa “entrelaza definición y uso”?
La nota al pie # 28 del SICP dice lo siguiente:
Las definiciones incrustadas deben ir primero en el cuerpo de un procedimiento. La gerencia no es responsable de las consecuencias de ejecutar programas que entrelazan definición y uso.
¿Qué significa esto exactamente? Entiendo:
- " definiciones " se refiere tanto a la creación de procedimientos como a la asignación de valores.
- " un organismo de procedimiento " , tal como se define en la sección 1.1.4 . Es el código que viene después de la lista de parámetros en la definición de un procedimiento.
- " Las definiciones incrustadas deben ir primero en el cuerpo de un procedimiento " para significar " cualquier definición que se haga y se use en el cuerpo de un procedimiento debe ir antes que cualquier otra cosa ".
- " entrelazar definición y uso " significa ' llamar a un procedimiento o valor asignado antes de que se haya definido ';
Sin embargo, esta comprensión parece contradecir las respuestas a esta pregunta , que tiene respuestas que puedo resumir como ' el error al que se refiere su cita es sobre el uso de definiciones al comienzo del cuerpo de un procedimiento que se basan en definiciones que también están en el comienzo de ese cuerpo '. Esto me confunde triplemente:
- Esa interpretación contradice claramente lo que he dicho anteriormente, pero parece tener evidencia sólida, reglas del compilador, detrás de ella.
- SICP parece feliz de poner una definición en un cuerpo con otras definiciones que los utilizan. ¡Solo mire el
sqrtprocedimiento justo encima de la nota al pie ! - De un vistazo, me parece que el verdadero error del autor de la pregunta vinculada fue tratarla
num-prodcomo un valor en su definición ennumlugar de como un procedimiento. Sin embargo, el autor claramente lo hizo funcionar, así que probablemente me equivoque.
Entonces, ¿qué está pasando realmente? ¿Dónde está el malentendido?
Respuestas
En la definición / código de un procedimiento dado,
- "definiciones internas" son formas que comienzan con
define. - "un cuerpo de procedimiento" son todas las demás formas después de las formas que comienzan con
define. - "Las definiciones incrustadas deben ir primero en un cuerpo de procedimiento" significa que todos los
defineformularios internos deben ir primero, luego todos los demás formularios internos. Una vez quedefineaparece un formulario no interno, no puede aparecer undefineformulario interno después de eso. - "Sin uso entrelazado" significa que no se debe usar ningún nombre antes de su definición. Imagina que todas las
defineformas internas están reunidas en un equivalenteletrecy siguen sus reglas.
Es decir, tenemos,
(define (proc args ...)
;; internal, or "embedded", definitions
(define a1 ...init1...)
(define a2 ...init2...)
......
(define an ...initn...)
;; procedure body
exp1 exp2 .... )
Cualquiera se puede usar en cualquiera de las expresiones, pero solo dentro de una expresión lambda. (*) De lo contrario se referiría al valor de while que se está definiendo, y eso está prohibido, ya que cualquier nombre se considera aún no definido mientras se evalúa alguna de las expresiones.aiinitjaiajaiinitj
(*) Recuerda que (define (foo x) ...x...)es lo mismo que (define foo (lambda (x) ...x...)). Es por eso que las definiciones en ese sqrtprocedimiento en el libro que menciona están bien: son todas lambdaexpresiones, y el uso de cualquier nombre dentro de una expresión lambda solo se referirá al valor de ese nombre cuando el valor de la expresión lambda, una función lambda, lo hará ser llamado, no cuando se está evaluando esa expresión lambda, produciendo esa función lambda que es su valor.
El libro es un poco vago al principio con la semántica precisa de su lenguaje, pero en Scheme el código anterior es equivalente a
(define proc
(lambda (args ...)
;; internal, or "embedded", definitions
(letrec ( (a1 ...init1...)
(a2 ...init2...)
......
(an ...initn...) )
;; procedure body
exp1 exp2 ....
)))
como se puede ver explicado, por ejemplo, aquí , aquí o aquí .
Por ejemplo,
;; or, equivalently,
(define (my-proc x) (define my-proc
(lambda (x)
(define (foo) a) (letrec ( (foo (lambda () a))
(define a x) (a x) )
;; my-proc's body ;; letrec's body
(foo)) (foo))))
primero evalúa la expresión lambda (lambda () a), y vincula el nombre fooal resultado, una función; esa función se referirá al valor de a cuando (foo)se llamará , por lo que está bien que haya una referencia adentro de esa expresión lambda, porque cuando se evalúa esa expresión lambda, no ase necesita ningún valor de inmediato, solo la referencia a su valor futuro , bajo el nombre a, está presente allí; es decir, el valor que a tendrá después de que se inicialicen todos los nombres letrecy letrecse ingrese el cuerpo de . O en otras palabras, cuando definese completen todos los s internos y my-procse ingrese el cuerpo propio del procedimiento .
Entonces vemos que fooestá definido, pero no se usa durante las inicializaciones; aestá definido pero no se utiliza durante las inicializaciones; así todo está bien. Pero si tuviéramos por ejemplo
(define (my-proc x)
(define (foo) x) ; `foo` is defined as a function
(define a (foo)) ; `foo` is used by being called as a function
a)
entonces aquí foose define y se usa durante las inicializaciones de los s internos o "embebidos" define; esto está prohibido en Scheme. Esto es sobre lo que advierte el libro: las definiciones internas solo pueden definir cosas, pero su uso debería retrasarse para más adelante, cuando hayamos terminado con los internos definey entremos en el cuerpo del procedimiento completo.
Esto es sutil y, como implican la nota al pie y la pregunta a la que hace referencia, las sutilezas pueden variar según la implementación de un idioma en particular.
Estos temas se tratarán con mucho más detalle más adelante en el libro (capítulos 3 y 4) y, en general, el texto evita el uso de definiciones internas para evitar estos problemas hasta que se examinen en detalle.
Una diferencia clave entre el código sobre la nota al pie:
(define (sqrt x)
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.001))
(define (improve guess)
(average guess (/ x guess)))
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess))))
(sqrt-iter 1.0))
y el código en la otra pregunta:
(define (pi-approx n)
(define (square x) (* x x))
(define (num-prod ind) (* (* 2 ind) (* 2 (+ ind 1)))) ; calculates the product in the numerator for a certain term
(define (denom-prod ind) (square (+ (* ind 2 ) 1))) ;Denominator product at index ind
(define num (product num-prod 1 inc n))
(define denom (product denom-prod 1 inc n))
es que todas las definiciones en el primero son definiciones de procedimiento , mientras que numy denomson definiciones de valor . El cuerpo de un procedimiento no se evalúa hasta que se llama a ese procedimiento. Pero una definición de valor se evalúa cuando se asigna el valor.
Con una definición de valor:
(define sum (add 2 2))
(add 2 2)Se evaluará cuando se evalúe la definición, si es así add, ya debe estar definida. Pero con una definición de procedimiento:
(define (sum n m) (add n m))
se asignará un objeto de procedimiento, sumpero el cuerpo del procedimiento aún no se ha evaluado, por lo que addno es necesario definir cuándo sumse define, pero debe ser antes de que sumse llame:
(sum 2 2)
Como digo, hay mucha sutileza y mucha variación, así que no estoy seguro de si lo siguiente es siempre cierto para cada variación del esquema, pero dentro del 'esquema SICP' se puede decir ...
Válido (orden de evaluación de defines no significativo):
;procedure body
(define (sum m n) (add m n))
(define (add m n) (+ m n))
(sum 2 2)
También válido:
;procedure body
(define (sum) (add 2 2))
(define (add m n) (+ m n))
(sum)
Normalmente no es válido (el orden de evaluación de defines es significativo):
;procedure body
(define sum (add 2 2))
(define (add m n) (+ m n))
Si lo siguiente es válido depende de la implementación:
;procedure body
(define (add m n) (+ m n))
(define sum (add 2 2))
Y finalmente un ejemplo de entrelazamiento de definición y uso , si esto funciona también depende de la implementación. IIRC, esto funcionaría con Scheme descrito en el Capítulo 4 del libro si se ha implementado el escaneo .
;procedure body
(sum)
(define (sum) (add 2 2))
(define (add m n) (+ m n))
Es complicado y sutil, pero los puntos clave son:
- las definiciones de valor se evalúan de manera diferente a las definiciones de procedimiento,
- El comportamiento dentro de los bloques puede ser diferente al de los bloques exteriores.
- hay variación entre las implementaciones de esquemas,
- el libro está diseñado para que no tenga que preocuparse mucho por esto hasta el Capítulo 3,
- el libro cubrirá esto en detalle en el Capítulo 4.
Descubrió una de las dificultades del esquema. Y de ceceo. Debido a este tipo de problema de la gallina y el huevo , no hay un solo ceceo, pero aparecieron muchos ceceos.
Si las formas de enlace no están presentes en el código en una letrec-logic en R5RS y letrec*-logic en R6RS, la semántica no está definida . Lo que significa que todo depende de la voluntad del implementador del esquema.
Consulte el documento Fixing Letrec: una implementación fiel pero eficiente de la construcción de enlace recursivo de Scheme .
Además, puede leer las discusiones de la lista de correo de 1986 , cuando no hubo un consenso general entre los diferentes implementadores del esquema.
Además, en el MIT se desarrollaron 2 versiones del esquema, la versión para estudiantes y la versión de desarrollo de los investigadores, y se comportaron de manera diferente en cuanto al orden de los defineformularios.