Common Lisp'de Dinamik Bağlama
Bu soru, Common Lisp kapsam belirlemenin bir uzantısıdır (dinamik ve sözcüksel)
Common Lisp'te kapsam belirleme ve kapsam kavramlarını okudum ve (umarım) anladım (bağlantı: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), ancak aşağıdaki üç örnekte kafamı bulamıyorum. Tüm örnekler SBCL / Slime / Emacs'ta yeni bir lisp oturumunda çalıştırılır.
Örnek 1: 5 ve 5'i yazdırır
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(fun1 5)
Örnek 2: 5 ve 100 yazdırır
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
Örnek 3: 5, 5 ve 100 yazdırır
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
x
Fun1'in neden her zaman 5 yazdırdığını anlıyorum (sözcük kapsamı nedeniyle, ancak yanılıyorsam lütfen düzeltin). Anlamadığım şey, neden fun2'nin Örnek 1'de 5, Örnek 2'de 100 ve yine Örnek 3'te 5 yazdırmasının nedeni ?
- Örnek 1: X, belirsiz kapsamında değişken bölgesi 5 olarak ayarlanır FUN1 ve buna göre FUN2 bu değeri erişimi. Bu doğru bir yorum mu?
- Örnek 2: x tarafından 100'e ayarlanır defvar , ama ne zaman neden 5'e yeniden ayarlanan olmak değildir fun1 denir? Bağlamaların işlevler çağrıldığında mı yoksa tanımlandığında mı gerçekleştiğini sanıyordum? Görünüşe göre, fun1 tanımlandığında x'in henüz bağlı olmadığı ve bu nedenle fun1'deki x'in bağlanması (sözcüksel olarak kapsama alınmış olan) programın geri kalanı tarafından görülmez ve ardından "global" bağlanma, sonraki defvar ile gerçekleşir . İşlev çağrısında davranış x nedeniyle sözcük içinde gölgeleme sonra mı FUN1 ama gölgelendirme hiçbir dinamik FUN2 ? Yani fun1 x'i ilk olarak tanımladığından ve o sırada "global" x görmediğinden, burada x'in iki farklı örneği vardır.
- Örnek 3: Burada, x küresel olarak ilk olarak ayarlandığından, hem fun1 hem de fun2, x'in aynı örneğini referans alıyor ve dolayısıyla değeri fun1 sırasında güncelleniyor ve fun2 sırasında da uygulanıyor (ikisi de 5)? Ayrıca, sonunda x'in değerini sorduğumda 100 alıyorum (neden? Fun2 5'i döndürürken?)
Guy Steel'in Common Lisp kitabından aşağıdaki alıntıyla bir ilgisi var, ancak kafamı karıştıramıyorum:
"Sözcüksel kapsam kullanan yapılar, her yürütmede kurulu her varlık için etkin bir şekilde yeni bir ad üretir. Bu nedenle dinamik gölgeleme gerçekleşemez (sözcüksel gölgeleme olabilir). Dinamik kapsam söz konusu olduğunda bu özellikle önemlidir."
Aşağıdaki ifade her zaman doğrudur (kaynak: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):
Lisp'teki bağlayıcı kural şudur: bir adın kullanımı, o adın hala geçerli olan en son bildirimine bağlıdır.
Bazı parçaları anlamaya başladım, ancak üç parçayı da bütünsel olarak anlayamıyorum, bu yüzden yardım edebilirseniz çok yardımcı olur.
Yanıtlar
Örnek 1: Sınırsız kapsamı olan bir değişken olan x, fun1'de 5'e ayarlanmıştır ve buna göre fun2 bu değere erişir. Bu doğru bir yorum mu?
Çoğunlukla, bunu genişletmeme izin verin.
Ne zaman xkadar beyan defvar, değişken özel olarak ilan ediliyor ve şimdi itibaren xolan her zaman özel bir değişken olarak görülen ve dinamik bağlı. Aradığın zaman:
(fun1 5)
Bağlayıcı fun1dönüş değeri her ikisi de, yani dinamik yapılır fun1ve fun2bağlanması mevcut dinamik dayanmaktadır x.
Örnek 2: [...] Yani fun1 x'i ilk olarak tanımladığından ve o anda "global" bir x görmediğinden, burada x'in iki farklı hali var.
Evet, ancak bu tüm tercümanlar için geçerli değildir (bkz.Sylwester'ın cevabı). Tanımladığınızda fun1, xözel olduğu bilinmez; bu, parametrenin bu noktadaki kapsamının xsözcüksel olduğu anlamına gelir . Sonraları, defvardeğerlendirilir, bağlanma xin fun1hala sözcük ve böyle çağrı olarak fun1küresel değişkenin bağlayıcı dinamiğini değiştirmez x.
Örnek 3: [...] Ayrıca, sonunda x'in değerini istediğimde 100 alıyorum (neden? Fun2 5 döndürürken?
Özel değişkenin belirsiz kapsamı vardır , her yerde görünürler, ancak bağlarının dinamik kapsamı vardır , bu da yalnızca onu kuran biçim olduğu sürece bağlayıcı bir yaşam anlamına gelir.
Burada, xen üst düzeyde sorduğunuzda , küresel olarak x100'e bağlı olan değere sahip olursunuz ; 5 değeri x, çağrı etkinken yalnızca geçici olarak bağlanır fun1.
Eskiden SETFbir bağlamayı değiştirdiyseniz, global bağlanmayı değiştirebilirdiniz, ancak bu, işlev uygulaması veya letbağlamalar sırasında olan şey değildir .
Kodunuza bazı ek açıklamalar:
örnek 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)
Örnek 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)
Örnek 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
Numaranız farklı uygulamalarda farklı çalışır. Örneğin. İşlevlerini anında derlemeyen CLISP'de, ilk iki örnekte tamamen aynı şekilde davranacak ve işlevleri ilerledikçe derlerseniz çıktınız ile tamamen aynı şekilde davranacaktır.
Dinamik kapsam, sözcük kapsamının geçerli olmadığı anlamına gelir:
(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
Çünkü *test*dinamiktir (global), aynı ada sahip bir yerel değişkeni değiştirmek, onun üzerine yazan kapsam ortadan kalkana kadar geçici olarak onun üzerine yazar. Dinamik olan budur.
Eğer *test*lexically kapsamlı edildi hem basacaktır 100.
Bu yüzden her zaman *earmuffs*globallerde kullanmalısınız . Bir değişkeni bir yerde bir parametre veya yerel değişken ile defvarveya bir yerde defparameteraynı şeyi kullanarak tanımladıysanız, değişkeni bilmeden geçici olarak değiştirebilirsiniz ve nerede olduğunu bulmak çok zor olabilir! İnsanlar *earmuffs*parametreleri letgördüklerinde ve niyetin bu olduğunu anladıklarında. Örneğin.
(with-output-to-string (*standard-output*)
(some-function-whose-printed-output-you-want))
; ==> a string with the actual output
Çağrılan işlev daha akıllıca değildir. Standart çıktıya yazdırdığını düşünüyor ama siz onu sarmaladınız ve yürütme sırasında çıktı akışını değiştirdiniz.