Enlace dinámico en Common Lisp

Oct 19 2020

Esta pregunta es una extensión del alcance de Common Lisp (dinámico vs léxico)

He leído y (con suerte) entendido los conceptos de alcance y extensión en Common Lisp (enlace: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), pero no puedo entender los siguientes tres ejemplos. Todos los ejemplos se ejecutan en una nueva sesión de lisp en SBCL / Slime / Emacs.

Ejemplo 1: impresiones 5 y 5

(defvar x 100)
(defun fun1 (x)
   (print x)
   (fun2))

(defun fun2 ()
   (print x))
     
(fun1 5)

Ejemplo 2: Imprime 5 y 100

 (defun fun1 (x)
   (print x)
   (fun2))

 (defun fun2 ()
   (print x))
    
 (defvar x 100)
 
 (fun1 5)

Ejemplo 3: Imprime 5, 5 y 100

(defvar x 100)

(defun fun1 (x)
  (print x)
  (fun2))

(defun fun2 ()
  (print x))

(defvar x 100)
     
(fun1 5)

x

Entiendo por qué fun1 siempre imprime 5 (debido al alcance léxico, pero corrija si me equivoco). Lo que no entiendo es por qué fun2 imprime 5 en el Ejemplo 1, 100 en el Ejemplo 2 y nuevamente 5 en el Ejemplo 3.

  • Ejemplo 1: x, una variable con alcance indefinido, se establece en 5 en fun1 y, en consecuencia, fun2 accede a este valor. ¿Es esta una interpretación correcta?
  • Ejemplo 2: x se establece en 100 mediante defvar , pero ¿por qué no se restablece en 5 cuando se llama a fun1 ? Pensé que los enlaces ocurrieron cuando se llaman las funciones o es cuando se definen. Parece que x aún no está vinculado cuando se define fun1 y, por lo tanto, el resto del programa no ve la vinculación de x en fun1 (que tiene un ámbito léxico) y luego la vinculación "global" tiene lugar con la defvar posterior . ¿El comportamiento x en la llamada a la función se debe entonces al sombreado léxico en fun1 pero no al sombreado dinámico para fun2 ? Es decir, hay dos instancias diferentes de x aquí, ya que fun1 definió su x primero y no vio una x "global" en ese momento.
  • Ejemplo 3: Aquí parece que dado que x se establece globalmente primero, tanto fun1 como fun2 hacen referencia a la misma instancia de x y, por lo tanto, su valor se actualiza durante fun1 y se aplica también durante fun2 (ambos son 5). Además, obtengo 100 cuando pregunto por el valor de x al final (¿por qué? ¿Cuándo fun2 devuelve 5?)

Tiene algo que ver con el siguiente extracto del libro Common Lisp de Guy Steel, pero no puedo entenderlo:

"Las construcciones que usan el alcance léxico generan efectivamente un nuevo nombre para cada entidad establecida en cada ejecución. Por lo tanto, el sombreado dinámico no puede ocurrir (aunque puede ocurrir el sombreado léxico). Esto es de particular importancia cuando se trata de extensión dinámica".

¿Es la siguiente afirmación siempre cierta (fuente: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):

La regla vinculante en Lisp es la siguiente: el uso de un nombre está vinculado a la declaración más reciente de ese nombre que aún está activa.

Estoy empezando a comprender algunas de las partes, pero no puedo obtener una comprensión integral de las tres partes, por lo que sería muy útil si pudiera ayudar.

Respuestas

4 coredump Oct 19 2020 at 04:18

Ejemplo 1: x, una variable con alcance indefinido, se establece en 5 en fun1 y, en consecuencia, fun2 accede a este valor. ¿Es esta una interpretación correcta?

Sobre todo, déjame ampliar eso.

Cuando xes declarada por defvarla variable se declara como especial, y de ahora en adelante xse siempre visto como una variable especial, y ligado de forma dinámica. Cuando usted llama:

(fun1 5)

La vinculación fun1se realiza de forma dinámica, lo que significa que tanto el valor de retorno de fun1y fun2se basan en la vinculación dinámica actual de x.

Ejemplo 2: [...] Es decir, hay dos instancias diferentes de x aquí ya que fun1 definió su x primero y no vio una x "global" en ese momento.

Sí, pero esto no es cierto en todos los intérpretes (consulte la respuesta de Sylwester). Cuando se define fun1, xno se sabe que sea especial; eso significa que el alcance en este punto para el parámetro xes léxico. Más tarde, cuando defvarse evalúa, la vinculación de xin fun1sigue siendo léxica y, como tal, la llamada fun1no modifica la vinculación dinámica de la variable global x.

Ejemplo 3: [...] Además, obtengo 100 cuando pregunto por el valor de x al final (¿por qué? ¿Cuando fun2 devuelve 5?

Las variables especiales tienen un alcance indefinido , son visibles en todas partes, pero sus vinculaciones tienen extensión dinámica , lo que significa que una vinculación vive solo mientras la forma que la establece.

Aquí, cuando solicita xen el nivel superior, tiene el valor que está globalmente vinculado a x100; el valor de 5 solo está vinculado temporalmente xmientras la llamada a fun1está en vigor.

Si solía SETFmutar un enlace, entonces podría mutar el enlace global, pero esto no es lo que sucede durante la aplicación de la función o los letenlaces.

5 RainerJoswig Oct 19 2020 at 15:03

Algunas anotaciones a su código:

Ejemplo 1

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))        

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X
     
(fun1 5)

Ejemplo 2

 (defun fun1 (x)  ; X is a lexical local variable
   (print x)      ; lexical reference to X
   (fun2))

 (defun fun2 ()
   (print x))     ; X is undeclared/undefined
                  ; the exact behaviour is undefined in Common Lisp
                  ; many implementations assume dynamic lookup of X
                  ; most compilers will show a warning
                  ; CMUCL also by default declared X globally to be special
                  ; -> don't use this in your code
    
 (defvar x 100)   ; declares X to be special, globally and locally 
                  ; also sets X to 100     
 (fun1 5)

Ejemplo 3

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X

(defvar x 100)    ; does nothing
                  ;  -> X is already declared special
                  ;  -> X already has a value
                  ;     see also: DEFPARAMETER
     
(fun1 5)

x                 ; lookup of global (or thread local) value of X
3 Sylwester Oct 19 2020 at 04:19

Tu truco funciona de manera diferente en diferentes implementaciones. P.ej. en CLISP, que no compila sus funciones sobre la marcha, se comportará exactamente igual en los dos primeros ejemplos y exactamente igual que su salida si compila las funciones sobre la marcha.

El alcance dinámico significa que el alcance léxico no se aplica:

(defparameter *test* 100)

(defun print-test ()
  (print *test*))

(defun call-print-test-with (*test*)
  (print-test))

(print-test)         ; prints 100
(call-print-test 10) ; prints 10

Debido a que *test*es dinámico (global), cambiar una variable local con el mismo nombre la sobrescribe temporalmente hasta que el alcance que la sobrescribe desaparece. Esto es lo que significa dinámica.

Si *test*tuviera un alcance léxico, ambos se imprimirían 100.

Es por eso que siempre debes usarlo *earmuffs*en globales. Si ha definido una variable en algún lugar con defvaro defparameterusando lo mismo como parámetro o variable local en algún lugar, puede cambiar la variable temporalmente sin saberlo y puede ser muy difícil encontrar dónde sucede. Cuando la gente ve los *earmuffs*parámetros y letentienden, esta es la intención. p.ej.

(with-output-to-string (*standard-output*)
  (some-function-whose-printed-output-you-want))
; ==> a string with the actual output

La función que se llama no es más sabia. Cree que está imprimiendo en stdout pero lo ha empaquetado y cambiado el flujo de salida durante su ejecución.