Dynamiczne wiązanie w Common Lisp
To pytanie jest rozszerzeniem zakresu Common Lisp (dynamiczne vs leksykalne)
Przeczytałem i (mam nadzieję) zrozumiałem pojęcia określania zakresu i zakresu w Common Lisp (link: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), ale nie jestem w stanie zrozumieć następujących trzech przykładów. Wszystkie przykłady są uruchamiane na nowej sesji lisp w SBCL / Slime / Emacs.
Przykład 1: Wydruki 5 i 5
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(fun1 5)
Przykład 2: Wydruki 5 i 100
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
Przykład 3: Wydruki 5, 5 i 100
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
x
Rozumiem, dlaczego fun1 zawsze wypisuje 5 (ze względu na zakres leksykalny, ale proszę poprawić, jeśli się mylę). Nie rozumiem, dlaczego fun2 wyświetla 5 w przykładzie 1, 100 w przykładzie 2 i ponownie 5 w przykładzie 3?
- Przykład 1: x, zmienna o nieokreślonym zakresie, jest ustawiana na 5 w fun1 i odpowiednio fun2 uzyskuje dostęp do tej wartości. Czy to poprawna interpretacja?
- Przykład 2: x jest ustawiane na 100 przez defvar , ale dlaczego nie jest resetowane do 5, gdy wywoływana jest funkcja fun1 ? Myślałem, że powiązania mają miejsce, gdy wywoływane są funkcje, czy też jest to podczas ich definiowania? Wygląda na to, że x nie jest jeszcze związany, kiedy funkcja fun1 jest zdefiniowana, a zatem wiązanie x w fun1 (które ma zasięg leksykalny) nie jest widoczne dla reszty programu, a następnie następuje „globalne” wiązanie z późniejszą zmienną defvar . Czy zachowanie x w wywołaniu funkcji jest zatem spowodowane leksykalnym cieniowaniem w fun1, ale nie ma dynamicznego cieniowania dla fun2 ? To znaczy, istnieją tutaj dwa różne wystąpienia x, ponieważ fun1 zdefiniowała swoje x jako pierwsza i nie widziała wtedy „globalnego” x.
- Przykład 3: Wydaje się, że skoro x jest ustawione globalnie jako pierwsze, zarówno fun1, jak i fun2 odwołują się do tego samego wystąpienia x, a zatem jego wartość jest aktualizowana podczas fun1 i stosowana również podczas fun2 (obie mają wartość 5)? Ponadto otrzymuję 100, gdy na końcu pytam o wartość x (dlaczego? Kiedy fun2 zwraca 5?)
Ma to coś wspólnego z następującym fragmentem książki Guy Steel's Common Lisp, ale nie mogę się tego obejść:
„Konstrukcje, które używają zakresu leksykalnego, efektywnie generują nową nazwę dla każdej ustalonej jednostki przy każdym wykonaniu. Dlatego też dynamiczne cieniowanie nie może wystąpić (chociaż może to robić). Ma to szczególne znaczenie, gdy w grę wchodzi zakres dynamiczny”.
Czy poniższe stwierdzenie jest zawsze prawdziwe (źródło: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):
Obowiązująca reguła w Lisp jest taka: użycie nazwy jest powiązane z najnowszą deklaracją tej nazwy, która jest nadal aktywna.
Zaczynam rozumieć niektóre części, ale nie mogę uzyskać pełnego zrozumienia wszystkich trzech części, więc byłoby bardzo pomocne, gdybyś mógł pomóc.
Odpowiedzi
Przykład 1: x, zmienna o nieokreślonym zakresie, jest ustawiana na 5 w fun1 i odpowiednio fun2 uzyskuje dostęp do tej wartości. Czy to poprawna interpretacja?
Przede wszystkim pozwól mi to rozwinąć.
Kiedy xjest zadeklarowane przez defvar, zmienna jest deklarowana jako specjalna i od tej pory xjest zawsze postrzegana jako zmienna specjalna i wiązana dynamicznie. Kiedy zadzwonisz:
(fun1 5)
Wiązanie fun1jest wykonywane dynamicznie, co oznacza, że zarówno wartość zwracana, jak fun1i fun2są oparte na bieżącym dynamicznym powiązaniu x.
Przykład 2: [...] To znaczy istnieją tutaj dwa różne wystąpienia x, ponieważ fun1 zdefiniowała swoje x jako pierwsza i nie widziała wówczas „globalnego” x.
Tak, ale nie dotyczy to wszystkich tłumaczy (patrz odpowiedź Sylwestra). Kiedy definiujesz fun1, xnie jest znana; oznacza to, że zakres parametru w tym miejscu xjest leksykalny. Później, gdy defvarjest oceniane, powiązanie xin fun1jest nadal leksykalne i jako takie wywołanie fun1nie modyfikuje dynamicznego wiązania zmiennej globalnej x.
Przykład 3: [...] Ponadto otrzymuję 100, kiedy pytam o wartość x na końcu (dlaczego? Kiedy fun2 zwraca 5?
Zmienne specjalne mają nieokreślony zasięg , są widoczne wszędzie, ale ich powiązania mają zasięg dynamiczny , co oznacza, że wiązanie żyje tylko tak długo, jak długo ustanawia je forma.
Tutaj, kiedy pytasz xna najwyższym poziomie, masz wartość, która jest globalnie ograniczona do x100; wartość 5 jest ograniczona tylko tymczasowo, xgdy wywołanie fun1jest aktywne.
Jeśli wcześniej SETFmodyfikowałeś powiązanie, możesz zmodyfikować globalne powiązanie, ale nie dzieje się tak podczas aplikacji funkcji lub letpowiązań.
Kilka adnotacji do Twojego kodu:
Przykład 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)
Przykład 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)
Przykład 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
Twoja sztuczka działa inaczej w różnych implementacjach. Na przykład. w CLISP, który nie kompiluje swoich funkcji w locie, będzie zachowywał się dokładnie tak samo w dwóch pierwszych przykładach i dokładnie tak samo, jak twoje dane wyjściowe, jeśli kompilujesz funkcje w trakcie.
Zakres dynamiczny oznacza, że zakres leksykalny nie ma zastosowania:
(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
Ponieważ *test*jest dynamiczna (globalna) zmiana lokalnej zmiennej o tej samej nazwie tymczasowo nadpisuje ją, dopóki zakres, który ją nadpisuje, nie zniknie. To właśnie oznacza dynamika.
Gdyby *test*zakres leksykalny był określony, oba wydrukowałyby 100.
Dlatego zawsze powinieneś używać *earmuffs*na globals. Jeśli gdzieś zdefiniowałeś zmienną z defvarlub defparameterużywając tego samego parametru lub zmiennej lokalnej gdzieś, możesz zmienić zmienną tymczasowo bez wiedzy i może być bardzo trudno znaleźć, gdzie to się dzieje! Kiedy ludzie widzą *earmuffs*parametry i letrozumieją, że to jest intencja. na przykład.
(with-output-to-string (*standard-output*)
(some-function-whose-printed-output-you-want))
; ==> a string with the actual output
Funkcja, która jest wywoływana, nie jest mądrzejsza. Myśli, że drukuje na stdout, ale opakowałeś go i zmieniłeś strumień wyjściowy podczas jego wykonywania.