Misalkan saya memiliki file bernama elisp-defvar-test.el
mengandung:
;;; elisp-defvar-test.el --- -*- lexical-binding: t -*-
(defvar my-dynamic-var)
(defun f1 (x)
"Should return X."
(let ((my-dynamic-var x))
(f2)))
(defun f2 ()
"Returns the current value of `my-dynamic-var'."
my-dynamic-var)
(provide 'elisp-dynamic-test)
;;; elisp-defvar-test.el ends here
Saya memuat file ini dan kemudian pergi ke buffer awal dan menjalankan:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
mengembalikan 5 seperti yang diharapkan, menunjukkan bahwa tubuh f1
memperlakukan my-dynamic-var
sebagai variabel cakupan dinamis, seperti yang diharapkan. Namun, formulir terakhir memberikan kesalahan variabel void my-dynamic-var
, yang menunjukkan bahwa ia menggunakan pelingkupan leksikal untuk variabel ini. Ini sepertinya bertentangan dengan dokumentasi untuk defvar
, yang mengatakan:
The
defvar
bentuk juga menyatakan variabel sebagai "khusus", sehingga selalu dinamis terikat bahkan jikalexical-binding
adalah t.
Jika saya mengubah defvar
formulir dalam file pengujian untuk memberikan nilai awal, maka variabel selalu diperlakukan sebagai dinamis, seperti yang dikatakan dokumentasi. Adakah yang bisa menjelaskan mengapa pelingkupan suatu variabel ditentukan oleh apakah defvar
diberi nilai awal atau tidak saat mendeklarasikan variabel itu?
Inilah backtrace kesalahan, jika itu penting:
Debugger entered--Lisp error: (void-variable my-dynamic-var)
f2()
(let ((my-dynamic-var 5)) (f2))
(progn (let ((my-dynamic-var 5)) (f2)))
eval((progn (let ((my-dynamic-var 5)) (f2))) t)
elisp--eval-last-sexp(t)
eval-last-sexp(t)
eval-print-last-sexp(nil)
funcall-interactively(eval-print-last-sexp nil)
call-interactively(eval-print-last-sexp nil nil)
command-execute(eval-print-last-sexp)
sumber
Jawaban:
Mengapa keduanya diperlakukan berbeda kebanyakan "karena itulah yang kami butuhkan". Lebih khusus lagi, bentuk argumen tunggal
defvar
muncul sejak lama, tetapi kemudian lebih dari yang lain dan pada dasarnya merupakan "retasan" untuk membungkam peringatan kompiler: pada waktu eksekusi itu tidak berpengaruh sama sekali, sehingga sebagai "kecelakaan" itu berarti bahwa perilaku membungkam(defvar FOO)
hanya diterapkan pada file saat ini (karena kompiler tidak memiliki cara untuk mengetahui bahwa defvar tersebut telah dieksekusi di beberapa file lain).Ketika
lexical-binding
diperkenalkan pada Emacs-24, kami memutuskan untuk kembali menggunakan ini(defvar FOO)
bentuk, tapi yang menyiratkan bahwa sekarang tidak memiliki efek.Sebagian untuk mempertahankan perilaku "hanya mempengaruhi file saat ini" sebelumnya, tetapi yang lebih penting untuk memungkinkan perpustakaan untuk digunakan
toto
sebagai var dengan cakupan dinamis tanpa mencegah perpustakaan lain dari penggunaantoto
sebagai var dengan cakupan leksikal (biasanya konvensi penamaan paket-awalan menghindari yang konflik, tetapi tidak digunakan di mana-mana dengan sedih), perilaku baru(defvar FOO)
didefinisikan hanya berlaku untuk file saat ini, dan bahkan disempurnakan sehingga hanya berlaku untuk lingkup saat ini (misalnya jika muncul dalam suatu fungsi, itu hanya mempengaruhi penggunaan var dalam fungsi itu).Pada dasarnya,
(defvar FOO VAL)
dan(defvar FOO)
hanya dua hal yang "sangat berbeda". Mereka kebetulan menggunakan kata kunci yang sama karena alasan historis.sumber
(defvar FOO)
membuat mode baru jauh lebih kompatibel dengan kode lama. Juga, IIRC masalah dengan solusi CommonLisp adalah bahwa itu cukup mahal untuk penerjemah murni seperti Elisp (misalnya setiap kali Anda mengevaluasilet
Anda harus melihat ke dalam tubuhnya jika adadeclare
yang mempengaruhi beberapa vars).Berdasarkan eksperimen, saya percaya masalahnya adalah bahwa
(defvar VAR)
tanpa nilai init hanya berpengaruh pada perpustakaan yang muncul di.Ketika saya menambahkan
(defvar my-dynamic-var)
ke*scratch*
buffer, kesalahan tidak lagi terjadi.Saya awalnya berpikir ini karena mengevaluasi formulir itu, tetapi saya kemudian perhatikan pertama-tama bahwa cukup mengunjungi file dengan formulir itu sudah cukup; dan lebih jauh lagi bahwa hanya menambahkan (atau menghapus) yang terbentuk di buffer, tanpa mengevaluasinya cukup untuk mengubah apa yang terjadi ketika mengevaluasi
(let ((my-dynamic-var 5)) (f2))
di dalam buffer yang sama dengannyaeval-last-sexp
.(Saya tidak memiliki pemahaman nyata tentang apa yang terjadi di sini. Saya merasa perilaku ini mengejutkan, tetapi saya tidak mengenal detail bagaimana fungsi ini diimplementasikan.)
Saya akan menambahkan bahwa bentuk
defvar
(tanpa nilai init) ini mencegah kompiler byte dari mengeluh tentang penggunaan variabel dinamis yang ditentukan secara eksternal dalam file elisp yang sedang dikompilasi, tetapi dengan sendirinya itu tidak menyebabkan variabel itu menjadiboundp
; jadi itu tidak sepenuhnya mendefinisikan variabel. (Perhatikan bahwa jika variabel ituboundp
maka masalah ini tidak akan terjadi sama sekali.)Dalam prakteknya Saya kira ini akan bekerja ok asalkan Anda tidak menyertakan
(defvar my-dynamic-var)
di perpustakaan leksikal mengikat yang menggunakan Andamy-dynamic-var
variabel (yang mungkin akan memiliki definisi yang sebenarnya di tempat lain).Edit:
Berkat pointer dari @npostavs di komentar:
Keduanya
eval-last-sexp
daneval-defun
digunakaneval-sexp-add-defvars
untuk:Secara khusus ini menempatkan semua
defvar
,defconst
dandefcustom
contoh. (Bahkan ketika berkomentar, saya perhatikan.)Saat ini sedang mencari buffer pada saat panggilan, itu menjelaskan bagaimana formulir ini dapat memiliki efek dalam buffer bahkan tanpa dievaluasi, dan mengkonfirmasi bahwa formulir tersebut harus muncul dalam file elisp yang sama (dan juga lebih awal dari kode yang dievaluasi) .
sumber
eval-sexp-add-defvars
memeriksa defvars dalam teks buffer.Saya tidak dapat mereproduksi ini sama sekali, mengevaluasi potongan terakhir berfungsi dengan baik di sini dan mengembalikan 5 seperti yang diharapkan. Apakah Anda yakin tidak mengevaluasi
my-dynamic-var
sendiri? Itu akan melempar kesalahan karena variabelnya batal, itu belum diatur ke nilai dan itu hanya akan memiliki satu jika Anda secara dinamis mengikatnya ke satu.sumber
lexical-binding
nihil sebelum mengevaluasi formulir? Saya mendapatkan perilaku yang Anda gambarkan denganlexical-binding
nil, tetapi ketika saya mengaturnya ke non-nil, saya mendapatkan kesalahan void-variabel.lexical-binding
diatur dan dievaluasi formulir secara berurutan.my-dynamic-var
nilai dinamis tingkat atas di sesi Anda saat ini? Saya pikir itu bisa menandainya spesial secara permanen.