50 Stimmen

Dynamische und lexikalische Variablen in Common Lisp

Ich lese gerade das Buch 'Practical Common Lisp' von Peter Seibel.

In Kapitel 6, "Variablen" Abschnitte "Lexikalische Variablen und Schließungen" und "Dynamische, d.h. spezielle, Variablen". http://www.gigamonkeys.com/book/variables.html

Mein Problem ist, dass die Beispiele in beiden Abschnitten zeigen, wie (let ...) globale Variablen schattieren kann und nicht wirklich den Unterschied zwischen den dynamischen und lexikalischen Variablen aufzeigt.

Ich verstehe, wie Verschlüsse funktionieren, aber ich verstehe nicht wirklich, was an let in diesem Beispiel so besonders ist:

(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

Ich habe das Gefühl, dass hier nichts Besonderes vor sich geht. Die äußere foo en bar inkrementiert die globale x y foo umgeben von lassen Sie en bar inkrementiert die schattierten x . Was ist daran so schlimm? Ich verstehe nicht, wie man damit den Unterschied zwischen lexikalischen und dynamischen Variablen erklären will. Doch das Buch geht so weiter:

Wie funktioniert das also? Woher weiß LET wissen, dass, wenn es bindet x Es soll soll eine dynamische Bindung schaffen und nicht eine normale lexikalische Bindung? Es weiß, weil der Name als speziell deklariert wurde.12 Der Name jeder Variablen, die mit DEFVAR und DEFPARAMETER definierten Variablen wird automatisch als global als speziell deklariert.

Was würde passieren, wenn lassen Sie würde binden x mit "normale lexikalische Bindung" ? Was sind die Unterschiede zwischen dynamischer und lexikalischer Bindung und was ist das Besondere an diesem Beispiel der dynamischen Bindung?

25voto

Kyle Cronin Punkte 74993

Wenn eine Variable lexikalisch skaliert sucht das System nach dem Ort, an dem sich die Funktion befindet definiert um den Wert für eine freie Variable zu finden. Wenn eine Variable dynamisch skaliert sucht das System nach dem Ort, an dem sich die Funktion befindet genannt. um den Wert für die freie Variable zu finden. Standardmäßig sind alle Variablen in Common Lisp lexikalisch; dynamisch skalierte Variablen können jedoch auf der obersten Ebene definiert werden, indem defvar o defparameter .

Ein einfacheres Beispiel

lexikalisches Scoping (mit setq):

(setq x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 3

dynamisches Scoping (mit defvar):

(defvar x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 4

Woher weiß das let, ob eine Variable lexikalisch oder dynamisch ist? Das ist nicht der Fall. Wenn foo hingegen den Wert von X sucht, findet es zunächst den lexikalischen Wert, der auf der obersten Ebene definiert ist. Dann prüft es, ob die Variable dynamisch sein soll. Ist dies der Fall, wendet sich foo an die aufrufende Umgebung, die in diesem Fall let verwendet, um den Wert von X auf 4 zu überschatten.

(Hinweis: Dies ist eine grobe Vereinfachung, aber sie hilft, den Unterschied zwischen den verschiedenen Scoping-Regeln zu verdeutlichen)

11voto

Mark Cox Punkte 400

Vielleicht ist dieses Beispiel hilfreich.

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

10voto

Leslie P. Polzer Punkte 2970

Sie können Ihr Lisp auch anweisen, lokale Variablen dynamisch zu binden:

(let ((dyn 5))
  (declare (special dyn))
  ... ;; DYN has dynamic scope for the duration of the body
  )

9voto

Juanito Fatas Punkte 8441

Rewrite-Beispiel aus 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

Eine weitere großartige Erklärung von Über Lisp , Kapitel 2.5 Geltungsbereich:

Common Lisp ist ein lexikalisch skaliertes Lisp. Scheme ist der älteste Dialekt mit lexikalischer Skalierung; vor Scheme galt die dynamische Skalierung als eines der bestimmenden Merkmale von Lisp.

Der Unterschied zwischen lexikalischem und dynamischem Geltungsbereich liegt darin, wie eine Implementierung mit freien Variablen umgeht. Ein Symbol ist in einem Ausdruck gebunden, wenn es als Variable festgelegt wurde, entweder indem es als Parameter erscheint oder durch Variablenbindungsoperatoren wie let und do. Symbole, die nicht gebunden sind, werden als frei bezeichnet. In diesem Beispiel kommt der Geltungsbereich ins Spiel:

(let ((y 7)) 
  (defun scope-test (x)
  (list x y)))

Innerhalb des defun-Ausdrucks ist x gebunden und y ist frei. Freie Variablen sind interessant, weil es nicht offensichtlich ist, welche Werte sie haben sollten. Es gibt keine Ungewissheit über den Wert einer gebundenen Variablen - wenn scope-test aufgerufen wird, sollte der Wert von x der sein, der als Argument übergeben wurde. Aber was sollte der Wert von y sein? Diese Frage wird durch die Scope-Regeln des Dialekts beantwortet.

In einem dynamisch skopierten Lisp müssen wir, um den Wert einer freien Variablen beim Ausführen von scope-test zu finden, die Kette der Funktionen zurückverfolgen, die sie aufgerufen haben. Wenn wir eine Umgebung finden, in der y gebunden war, wird diese Bindung von y diejenige sein, die in scope-test verwendet wird. Wenn wir keine finden, nehmen wir den globalen Wert von y. In einem dynamisch skalierten Lisp würde y also den Wert haben, den es im aufrufenden Ausdruck hatte:

> (let ((y 5)) (scope-test 3))
    (3 5)

Bei dynamischem Geltungsbereich bedeutet es nichts, dass y an 7 gebunden war, als scope-test definiert wurde. Wichtig ist nur, dass y zum Zeitpunkt des Aufrufs von scope-test einen Wert von 5 hatte.

In einem lexikalisch skalierten Lisp blicken wir nicht durch die Kette der aufrufenden Funktionen zurück, sondern durch die enthaltenden Umgebungen zum Zeitpunkt der Funktionsdefinition. In einem lexikalisch skopierten Lisp würde unser Beispiel die Bindung von y abfangen, wo scope-test definiert wurde. So würde es also in Common Lisp ablaufen:

> (let ((y 5)) (scope-test 3))
    (3 7)

Hier hat die Bindung von y an 5 zum Zeitpunkt des Aufrufs keine Auswirkungen auf den zurückgegebenen Wert.

Obwohl Sie immer noch einen dynamischen Geltungsbereich erhalten können, indem Sie eine Variable als speziell deklarieren, ist der lexikalische Geltungsbereich der Standard in Common Lisp. Im Großen und Ganzen scheint die Lisp-Gemeinschaft die Abschaffung des dynamischen Geltungsbereichs mit wenig Bedauern zu betrachten. Zum einen führte er früher zu schrecklich schwer fassbaren Fehlern. Aber der lexikalische Bereich ist mehr als nur ein Weg, um Fehler zu vermeiden. Wie der nächste Abschnitt zeigen wird, ermöglicht er auch einige neue Programmiertechniken.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X