programación - Variables dinámicas y léxicas en Common Lisp
lenguajes de programacion scheme (5)
Estoy leyendo el libro ''Practical Common Lisp'' de Peter Seibel.
En el Capítulo 6, secciones "Variables", "Variables y cierres léxicos" y "Variables dinámicas, también conocidas como especiales". http://www.gigamonkeys.com/book/variables.html
Mi problema es que los ejemplos en ambas secciones muestran cómo (let ...) puede sombrear variables globales y realmente no dice la diferencia entre los vars dinámicos y léxicos.
Entiendo cómo funcionan los cierres, pero realmente no entiendo qué es tan especial sobre este ejemplo:
(defvar *x* 10)
(defun foo ()
(format t "Before assignment~18tX: ~d~%" *x*)
(setf *x* (+ 1 *x*))
(format t "After assignment~18tX: ~d~%" *x*))
(defun bar ()
(foo)
(let ((*x* 20)) (foo))
(foo))
CL-USER> (foo)
Before assignment X: 10
After assignment X: 11
NIL
CL-USER> (bar)
Before assignment X: 11
After assignment X: 12
Before assignment X: 20
After assignment X: 21
Before assignment X: 12
After assignment X: 13
NIL
Siento que no hay nada especial está pasando aquí. El foo exterior en la barra incrementa la x global, y foo rodeado por la barra de entrada incrementa la x sombreada. ¿Cual es el problema? No veo cómo se supone que esto explica la diferencia entre vars léxicos y dinámicos. Sin embargo, el libro continúa así:
Entonces, ¿cómo funciona esto? ¿Cómo sabe LET que cuando se une x se supone que crea un enlace dinámico en lugar de un enlace léxico normal? Lo sabe porque el nombre ha sido declarado especial.12 El nombre de cada variable definida con DEFVAR y DEFPARAMETER se declara automáticamente globalmente especial.
¿Qué pasaría si dejáramos unir x usando "unión léxica normal" ? Con todo, ¿cuáles son las diferencias entre el enlace dinámico y el léxico y cómo es este ejemplo especial con respecto a la vinculación dinámica?
Cuando una variable tiene un alcance léxico , el sistema busca dónde se define la función para encontrar el valor de una variable libre. Cuando una variable tiene un ámbito dinámico , el sistema busca dónde se llama a la función para encontrar el valor de la variable libre. Las variables en Common Lisp son todas léxicas por defecto; sin embargo, las variables de ámbito dinámico se pueden definir en el nivel superior usando defvar o defparameter .
Un ejemplo más simple
Alcance léxico (con setq):
(setq x 3)
(defun foo () x)
(let ((x 4)) (foo)) ; returns 3
alcance dinámico (con defvar):
(defvar x 3)
(defun foo () x)
(let ((x 4)) (foo)) ; returns 4
¿Cómo sabe si una variable es léxica o dinámica? No es así Por otro lado, cuando foo busca el valor de X, inicialmente encontrará el valor léxico definido en el nivel superior. Luego verifica si la variable se supone que es dinámica. Si es así, entonces foo mira hacia el entorno de llamada, que, en este caso, usa dejar que eclipse el valor de X para que sea 4.
(nota: esto es una simplificación excesiva, pero ayudará a visualizar la diferencia entre las diferentes reglas de alcance)
También puedes decirle a tu Lisp que enlace variables locales dinámicamente:
(let ((dyn 5))
(declare (special dyn))
... ;; DYN has dynamic scope for the duration of the body
)
Tal vez este ejemplo ayude.
;; the lexical version
(let ((x 10))
(defun lex-foo ()
(format t "Before assignment~18tX: ~d~%" x)
(setf x (+ 1 x))
(format t "After assignment~18tX: ~d~%" x)))
(defun lex-bar ()
(lex-foo)
(let ((x 20)) ;; does not do anything
(lex-foo))
(lex-foo))
;; CL-USER> (lex-bar)
;; Before assignment X: 10
;; After assignment X: 11
;; Before assignment X: 11
;; After assignment X: 12
;; Before assignment X: 12
;; After assignment X: 13
;; the dynamic version
(defvar *x* 10)
(defun dyn-foo ()
(format t "Before assignment~18tX: ~d~%" *x*)
(setf *x* (+ 1 *x*))
(format t "After assignment~18tX: ~d~%" *x*))
(defun dyn-bar()
(dyn-foo)
(let ((*x* 20))
(dyn-foo))
(dyn-foo))
;; CL-USER> (dyn-bar)
;; Before assignment X: 10
;; After assignment X: 11
;; Before assignment X: 20
;; After assignment X: 21
;; Before assignment X: 11
;; After assignment X: 12
;; the special version
(defun special-foo ()
(declare (special *y*))
(format t "Before assignment~18tX: ~d~%" *y*)
(setf *y* (+ 1 *y*))
(format t "After assignment~18tX: ~d~%" *y*))
(defun special-bar ()
(let ((*y* 10))
(declare (special *y*))
(special-foo)
(let ((*y* 20))
(declare (special *y*))
(special-foo))
(special-foo)))
;; CL-USER> (special-bar)
;; Before assignment X: 10
;; After assignment X: 11
;; Before assignment X: 20
;; After assignment X: 21
;; Before assignment X: 11
;; After assignment X: 12
Vuelva a escribir el ejemplo de PCL.
;;; Common Lisp is lexically scoped by default.
λ (setq x 10)
=> 10
λ (defun foo ()
(setf x (1+ x)))
=> FOO
λ (foo)
=> 11
λ (let ((x 20))
(foo))
=> 12
λ (proclaim ''(special x))
=> NIL
λ (let ((x 20))
(foo))
=> 21
Otra gran explicación de On Lisp , capítulo 2.5 Alcance:
Common Lisp es un Lisp de ámbito léxico. El esquema es el dialecto más antiguo con alcance léxico; antes de Scheme, el alcance dinámico se consideraba una de las características definitorias de Lisp.
La diferencia entre el alcance léxico y dinámico se reduce a cómo una implementación trata con variables libres. Un símbolo está vinculado en una expresión si se ha establecido como una variable, ya sea al aparecer como un parámetro, o mediante operadores de enlace variable como let y do. Se dice que los símbolos que no están vinculados son gratuitos. En este ejemplo, el alcance entra en juego:
(let ((y 7))
(defun scope-test (x)
(list x y)))
Dentro de la expresión defun, x está vinculado e y es libre. Las variables gratuitas son interesantes porque no es obvio cuáles deberían ser sus valores. No hay incertidumbre sobre el valor de una variable vinculada: cuando se invoca el alcance-prueba, el valor de x debe ser lo que se pase como argumento. Pero, ¿cuál debería ser el valor de y? Esta es la pregunta respondida por las reglas de alcance del dialecto.
En un Lisp de ámbito dinámico, para encontrar el valor de una variable libre al ejecutar el alcance-prueba, miramos hacia atrás a través de la cadena de funciones que lo llamó. Cuando encontramos un entorno donde y estaba vinculado, ese enlace de y será el usado en el alcance-prueba. Si no encontramos ninguno, tomamos el valor global de y. Por lo tanto, en un Lisp de ámbito dinámico, y tendría el valor que tenía en la expresión de llamada:
> (let ((y 5)) (scope-test 3))
(3 5)
Con el alcance dinámico, no significa nada que y se vinculó a 7 cuando se definió el alcance-prueba. Lo único que importa es que y tenía un valor de 5 cuando se llamó a la prueba de alcance.
En un Lisp de ámbito léxico, en lugar de mirar hacia atrás a través de la cadena de funciones de llamada, miramos hacia atrás a través de los entornos en el momento en que se definió la función. En un Lisp de ámbito léxico, nuestro ejemplo capturaría el enlace de y donde se definió el alcance-prueba. Entonces esto es lo que sucedería en Common Lisp:
> (let ((y 5)) (scope-test 3))
(3 7)
Aquí, el enlace de y a 5 en el momento de la llamada no tiene efecto sobre el valor devuelto.
Aunque todavía puede obtener un alcance dinámico al declarar que una variable es especial, el alcance léxico es el predeterminado en Common Lisp. En general, la comunidad Lisp parece ver el paso del alcance dinámico con poca pena. Por un lado, solía llevar a errores horriblemente difíciles de alcanzar. Pero el alcance léxico es más que una forma de evitar errores. Como se mostrará en la siguiente sección, también hace posible algunas nuevas técnicas de programación.
¿Que esta pasando?
Usted dice: siente que no pasa nada especial aquí. El foo
exterior en la bar
incrementa la x
global, y foo
rodeado por la bar
incrementa la x
sombreada. ¿Cual es el problema?
Lo especial que está sucediendo aquí es que LET
puede sombrear el valor de *x*
. Con las variables léxicas eso no es posible.
El código declara que *x*
es especial a través del DEFVAR
.
En FOO
ahora el valor de *x*
se busca dinámico. FOO
tomará el enlace dinámico actual de *x*
o, si no hay ninguno, el valor del símbolo *x*
. Una nueva vinculación dinámica puede, por ejemplo, introducirse con LET
.
Por otro lado, una variable léxica tiene que estar presente en el entorno léxico en alguna parte. LET
, LAMBDA
, DEFUN
y otros pueden introducir tales variables léxicas. Vea aquí la variable léxica x
presentada de tres maneras diferentes:
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
Si nuestro código fuera:
(defvar x 0)
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
Entonces X
fue especial en los tres casos anteriores, debido a la declaración DEFVAR
, que declara que X
es especial , globalmente para todos los niveles. Debido a esto, existe la convención de declarar variables especiales como *X*
. Por lo tanto, solo las variables con estrellas a su alrededor son especiales , por convención . Esa es una convención útil.
En tu código tienes entonces:
(defun bar ()
(foo)
(let ((*x* 20))
(foo))
(foo))
Como *x*
ha declarado especial a través del DEFVAR
anterior en su código, el constructo LET
introduce un nuevo enlace dinámico para *x*
. FOO
es llamado. Dado que dentro de FOO
el *x*
usa enlace dinámico , busca el actual y encuentra que *x*
está dinámicamente ligado a 20
.
El valor de una variable especial se encuentra en el enlace dinámico actual.
Declaraciones ESPECIALES locales
También hay declaraciones special
locales:
(defun foo-s ()
(declare (special *x*))
(+ *x* 1))
Si la variable ha sido declarada especial por un DEFVAR
o DEFPARAMETER
, entonces la declaración special
local puede ser omitida.
Una variable léxica hace referencia directamente al enlace variable:
(defun foo-l (x)
(+ x 1))
Veámoslo en la práctica:
(let ((f (let ((x 10))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(print (funcall f))))
Aquí todas las variables son léxicas. En la forma 2, el LET
no sombreará la X
en nuestra función f
. No puede. La función usa la variable ligada léxica, introducida por LET ((X 10)
. Rodear la llamada con otra X
ligada léxicamente en la forma 2 no tiene ningún efecto en nuestra función.
Probemos variables especiales
(let ((f (let ((x 10))
(declare (special x))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(declare (special x))
(print (funcall f))))
¿Ahora que? ¿Eso funciona?
¡No es asi!
El primer formulario llama a la función e intenta buscar el valor dinámico de X
y no hay ninguno. Obtenemos un error en el formulario 1 : X
está desatado, porque no hay un enlace dinámico en vigor.
El formulario 2 funcionaría, ya que el LET
con la declaración special
introduce un enlace dinámico para X