Dynamic Binding di Common Lisp
Pertanyaan ini merupakan perpanjangan dari cakupan Lisp Umum (dinamis vs leksikal)
Saya telah membaca dan (semoga) memahami konsep pelingkupan dan perluasan di Common Lisp (tautan: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), tetapi saya tidak dapat memahami tiga contoh berikut. Semua contoh dijalankan pada sesi cadel baru di SBCL / Slime / Emacs.
Contoh 1: Cetakan 5 & 5
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(fun1 5)
Contoh 2: Cetakan 5 & 100
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
Contoh 3: Mencetak 5 & 5 & 100
(defvar x 100)
(defun fun1 (x)
(print x)
(fun2))
(defun fun2 ()
(print x))
(defvar x 100)
(fun1 5)
x
Saya mengerti mengapa fun1 selalu mencetak 5 (karena lingkup leksikal, tapi tolong perbaiki jika saya salah). Yang tidak saya mengerti adalah mengapa fun2 mencetak 5 di Contoh 1, 100 di Contoh 2 dan lagi 5 di Contoh 3?
- Contoh 1: x, variabel dengan cakupan tak terbatas, disetel ke 5 di fun1 dan sesuai dengan itu fun2 mengakses nilai ini. Apakah ini interpretasi yang benar?
- Contoh 2: x disetel ke 100 oleh defvar , tetapi mengapa tidak disetel ulang ke 5 saat fun1 dipanggil? Saya pikir binding terjadi saat fungsi dipanggil atau apakah saat ditentukan? Tampaknya x belum terikat saat fun1 didefinisikan, dan oleh karena itu pengikatan x dalam fun1 (yang secara leksikal dibatasi) tidak terlihat oleh program lainnya dan kemudian pengikatan "global" berlangsung dengan defvar berikutnya . Apakah perilaku x dalam pemanggilan fungsi kemudian disebabkan oleh bayangan leksikal di fun1 tetapi tidak ada bayangan dinamis untuk fun2 ? Yaitu ada dua contoh x yang berbeda di sini karena fun1 mendefinisikan x-nya terlebih dahulu dan tidak melihat x "global" pada saat itu.
- Contoh 3: Tampak di sini bahwa karena x disetel secara global terlebih dahulu, fun1 dan fun2 mereferensikan turunan x yang sama dan karenanya nilainya diperbarui selama fun1 dan diterapkan juga selama fun2 (keduanya adalah 5)? Selanjutnya saya mendapatkan 100 ketika saya meminta nilai x di akhir (mengapa? Ketika fun2 mengembalikan 5?)
Ini ada hubungannya dengan kutipan berikut dari buku Lisp Umum Guy Steel, tapi saya tidak bisa memahaminya:
"Konstruksi yang menggunakan ruang lingkup leksikal secara efektif menghasilkan nama baru untuk setiap entitas yang ditetapkan pada setiap eksekusi. Oleh karena itu bayangan dinamis tidak dapat terjadi (meskipun bayangan leksikal mungkin). Ini sangat penting ketika tingkat dinamis terlibat."
Apakah pernyataan berikut selalu benar (sumber: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):
Aturan pengikatan di Lisp adalah ini: penggunaan nama terikat pada deklarasi terbaru dari nama yang masih aktif.
Saya mulai memahami beberapa bagian, tetapi tidak dapat memperoleh pemahaman menyeluruh tentang ketiga bagian tersebut, jadi akan sangat membantu jika Anda dapat membantu.
Jawaban
Contoh 1: x, variabel dengan cakupan tak terbatas, disetel ke 5 di fun1 dan sesuai dengan itu fun2 mengakses nilai ini. Apakah ini interpretasi yang benar?
Sebagian besar, izinkan saya mengembangkannya.
Ketika xdinyatakan oleh defvar, variabel tersebut dinyatakan sebagai khusus, dan dari sekarang xini selalu dilihat sebagai variabel khusus, dan terikat secara dinamis. Saat Anda menelepon:
(fun1 5)
Pengikatan di fun1dilakukan secara dinamis, yang berarti nilai kembalian fun1dan fun2didasarkan pada pengikatan dinamis saat ini x.
Contoh 2: [...] Yaitu ada dua contoh x yang berbeda di sini karena fun1 mendefinisikan x-nya terlebih dahulu dan tidak melihat x "global" pada saat itu.
Ya, tetapi ini tidak benar di semua penafsir (lihat jawaban Sylwester). Ketika Anda mendefinisikan fun1, xtidak dikenal khusus; itu berarti ruang lingkup pada saat ini untuk parameter xadalah leksikal. Kemudian, saat defvardievaluasi, pengikatan xin fun1masih bersifat leksikal, dan karena itu pemanggilan fun1tersebut tidak mengubah pengikatan dinamis variabel global x.
Contoh 3: [...] Selanjutnya saya mendapatkan 100 ketika saya menanyakan nilai x di akhir (mengapa? Ketika fun2 mengembalikan 5?
Variabel khusus memiliki ruang lingkup yang tidak terbatas , terlihat dimana-mana, tetapi ikatannya memiliki jangkauan dinamis , yang berarti suatu ikatan hanya hidup selama bentuk yang menetapkannya.
Di sini, ketika Anda meminta xdi tingkat atas, Anda memiliki nilai yang terikat secara global ke x, 100; nilai 5 hanya terikat xsementara saat panggilan ke fun1berlaku.
Jika Anda biasa SETFmemutasi pengikatan, maka Anda bisa mengubah pengikatan global, tapi ini bukan yang terjadi selama letpengikatan atau aplikasi fungsi .
Beberapa anotasi pada kode Anda:
Contoh 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)
Contoh 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)
Contoh 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
Trik Anda bekerja secara berbeda dalam implementasi yang berbeda. Misalnya. di CLISP, yang tidak mengkompilasi fungsinya dengan cepat, akan berperilaku sama persis dalam dua contoh pertama dan persis sama dengan keluaran Anda jika Anda mengompilasi fungsi sambil jalan.
Cakupan dinamis berarti cakupan leksikal tidak berlaku:
(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
Karena *test*bersifat dinamis (global) mengubah variabel lokal dengan nama yang sama menimpanya untuk sementara hingga cakupan yang menimpanya hilang. Inilah yang dimaksud dengan dinamis.
Jika *test*dicakup secara leksikal keduanya akan mencetak 100.
Inilah mengapa Anda harus selalu menggunakannya *earmuffs*di global. Jika Anda telah mendefinisikan variabel di suatu tempat dengan defvaratau defparametermenggunakan yang sama sebagai parameter atau variabel lokal di suatu tempat, Anda mungkin mengubah variabel sementara tanpa mengetahui dan mungkin sangat sulit untuk menemukan di mana itu terjadi! Ketika orang melihat *earmuffs*parameter dan letmereka mengerti inilah tujuannya. misalnya.
(with-output-to-string (*standard-output*)
(some-function-whose-printed-output-you-want))
; ==> a string with the actual output
Fungsi yang disebut non the wiser. Ia mengira sedang mencetak ke stdout tetapi Anda telah membungkusnya dan mengubah aliran keluaran selama eksekusinya.