Dynamische Bindung in Common Lisp

Oct 19 2020

Diese Frage ist eine Erweiterung des Common-Lisp-Scoping (dynamisch oder lexikalisch).

Ich habe die Konzepte von Umfang und Umfang in Common Lisp gelesen und (hoffentlich) verstanden (Link: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), aber ich kann mich nicht mit den folgenden drei Beispielen auseinandersetzen. Alle Beispiele werden in einer neuen Lisp-Sitzung in SBCL / Slime / Emacs ausgeführt.

Beispiel 1: Druckt 5 und 5

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

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

Beispiel 2: Druckt 5 und 100

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

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

Beispiel 3: Druckt 5 & 5 & 100

(defvar x 100)

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

(defun fun2 ()
  (print x))

(defvar x 100)
     
(fun1 5)

x

Ich verstehe, warum fun1 immer 5 druckt (aufgrund des lexikalischen Umfangs, aber bitte korrigieren, wenn ich falsch liege ). Was ich nicht verstehe ist, warum fun2 5 in Beispiel 1, 100 in Beispiel 2 und erneut 5 in Beispiel 3 druckt?

  • Beispiel 1: x, eine Variable mit unbestimmtem Gültigkeitsbereich, wird in fun1 auf 5 gesetzt, und dementsprechend greift fun2 auf diesen Wert zu. Ist das eine korrekte Interpretation?
  • Beispiel 2: x wird von defvar auf 100 gesetzt , aber warum wird es nicht auf 5 zurückgesetzt, wenn fun1 aufgerufen wird? Ich dachte, die Bindungen haben stattgefunden, als die Funktionen aufgerufen wurden, oder ist es, als sie definiert wurden? Es scheint, dass x noch nicht gebunden ist, wenn fun1 definiert ist, und daher wird die Bindung von x in fun1 (die lexikalisch definiert ist) vom Rest des Programms nicht gesehen, und dann findet die "globale" Bindung mit der nachfolgenden defvar statt . Liegt das Verhalten x im Funktionsaufruf dann an lexikalischem Shadowing in fun1, aber nicht an dynamischem Shadowing für fun2 ? Dh es gibt hier zwei verschiedene Instanzen von x, da fun1 zuerst sein x definiert hat und zu diesem Zeitpunkt kein "globales" x gesehen hat.
  • Beispiel 3: Da x zuerst global festgelegt wird, verweisen sowohl fun1 als auch fun2 auf dieselbe Instanz von x, und daher wird sein Wert während fun1 aktualisiert und auch während fun2 angewendet (beide sind 5). Außerdem bekomme ich 100, wenn ich am Ende nach dem Wert von x frage (warum? Wenn fun2 5 zurückgibt?)

Es hat etwas mit dem folgenden Auszug aus Guy Steel's Common Lisp-Buch zu tun, aber ich kann mich nicht darum kümmern:

"Konstrukte, die den lexikalischen Bereich verwenden, generieren bei jeder Ausführung effektiv einen neuen Namen für jede etablierte Entität. Daher kann kein dynamisches Shadowing auftreten (obwohl lexikalisches Shadowing dies kann). Dies ist von besonderer Bedeutung, wenn es um das dynamische Ausmaß geht."

Ist die folgende Aussage immer wahr (Quelle: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):

Die verbindliche Regel in Lisp lautet: Die Verwendung eines Namens ist an die letzte Deklaration dieses Namens gebunden, die noch aktiv ist.

Ich fange an, einige der Teile zu verstehen, kann aber nicht alle drei Teile vollständig verstehen, daher wäre es sehr hilfreich, wenn Sie helfen könnten.

Antworten

4 coredump Oct 19 2020 at 04:18

Beispiel 1: x, eine Variable mit unbestimmtem Gültigkeitsbereich, wird in fun1 auf 5 gesetzt, und dementsprechend greift fun2 auf diesen Wert zu. Ist das eine korrekte Interpretation?

Lassen Sie mich das meistens näher erläutern.

Wenn xdurch deklariert wird defvar, wird die Variable wird als Sondermüll deklariert, und von nun an xwird immer als eine besondere Variable gesehen und dynamisch gebunden. Wenn du anrufst:

(fun1 5)

Die Bindung fun1erfolgt dynamisch, dh der Rückgabewert von fun1und fun2basiert auf der aktuellen dynamischen Bindung von x.

Beispiel 2: [...] Dh es gibt hier zwei verschiedene Instanzen von x, da fun1 zuerst sein x definiert hat und zu diesem Zeitpunkt kein "globales" x gesehen hat.

Ja, aber das gilt nicht für alle Dolmetscher (siehe Sylvesters Antwort). Wenn Sie definieren fun1, xist nicht bekannt, etwas Besonderes zu sein; Das bedeutet, dass der Gültigkeitsbereich für Parameter an dieser Stelle xlexikalisch ist. Später, wenn defvarausgewertet wird, ist die Bindung von xin fun1immer noch lexikalisch, und als solches fun1ändert der Aufruf die dynamische Bindung der globalen Variablen nicht x.

Beispiel 3: [...] Außerdem bekomme ich 100, wenn ich am Ende nach dem Wert von x frage (warum? Wenn fun2 5 zurückgibt?

Spezielle Variablen haben einen unbestimmten Geltungsbereich , sie sind überall sichtbar, aber ihre Bindungen haben eine dynamische Ausdehnung , was bedeutet, dass eine Bindung nur so lange lebt, wie die Form, die sie festlegt.

Wenn Sie hier xauf oberster Ebene nachfragen, haben Sie den Wert, der global an x100 gebunden ist. Der Wert 5 ist nur vorübergehend gebunden, xwährend der Aufruf von ausgeführt fun1wird.

Wenn Sie SETFeine Bindung mutiert haben, können Sie die globale Bindung mutieren. Dies geschieht jedoch nicht während der Funktionsanwendung oder der Bindung let.

5 RainerJoswig Oct 19 2020 at 15:03

Einige Anmerkungen zu Ihrem Code:

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

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

Beispiel 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

Ihr Trick funktioniert in verschiedenen Implementierungen unterschiedlich. Z.B. In CLISP, das seine Funktionen nicht im laufenden Betrieb kompiliert, verhält es sich in den beiden ersten Beispielen genauso und genau wie Ihre Ausgabe, wenn Sie die Funktionen unterwegs kompilieren.

Dynamischer Bereich bedeutet, dass der lexikalische Bereich nicht gilt:

(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

Da *test*das dynamische (globale) Ändern einer lokalen Variablen mit demselben Namen diese vorübergehend überschreibt, bis der Bereich, der sie überschreibt, nicht mehr vorhanden ist. Das bedeutet Dynamik.

Wenn *test*lexikalisch festgelegt wäre, würden beide drucken 100.

Aus diesem Grund sollten Sie immer *earmuffs*auf Globals verwenden. Wenn Sie irgendwo eine Variable mit einem Parameter oder einer lokalen Variablen definiert haben defvaroder diese defparameterverwenden, können Sie die Variable vorübergehend ändern, ohne es zu wissen, und es ist möglicherweise sehr schwer zu finden, wo dies geschieht! Wenn Menschen *earmuffs*in Parametern sehen und letverstehen, ist dies die Absicht. z.B.

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

Die aufgerufene Funktion ist nicht klüger. Es wird angenommen, dass auf stdout gedruckt wird, aber Sie haben es verpackt und den Ausgabestream während der Ausführung geändert.