Предположим, у меня есть файл с именем elisp-defvar-test.el
:
;;; 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
Я загружаю этот файл, затем захожу в рабочий буфер и запускаю:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
возвращает 5, как и ожидалось, указывая, что тело f1
обрабатывается my-dynamic-var
как переменная с динамической областью, как и ожидалось. Тем не менее, последняя форма выдает ошибку void-variable my-dynamic-var
, указывая на то, что она использует лексическую область видимости для этой переменной. Это, кажется, расходится с документацией defvar
, которая гласит:
defvar
Форма также объявляет переменную как «специальный», так что она всегда динамически связана , даже еслиlexical-binding
это т.
Если я изменю defvar
форму в тестовом файле, чтобы указать начальное значение, то переменная всегда обрабатывается как динамическая, как сказано в документации. Кто-нибудь может объяснить, почему область видимости переменной определяется тем, было ли defvar
задано начальное значение при объявлении этой переменной?
Вот обратная трассировка ошибки, если она имеет значение:
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)
источник
Ответы:
Почему к обоим относятся по-разному, в основном «потому что это то, что нам было нужно». Точнее говоря, форма с одним аргументом
defvar
появилась очень давно, но позже, чем другие, и была в основном «хаком», чтобы заставить замолчать предупреждения компилятора: во время выполнения она вообще не имела никакого эффекта, поэтому как «случайность» она означала что поведение молчания(defvar FOO)
применяется только к текущему файлу (поскольку компилятор не может знать, что такой defvar был выполнен в каком-то другом файле).Когда
lexical-binding
был введен в Emacs-24, мы решили повторно использовать эту(defvar FOO)
форму, но это означает , что он теперь делает иметь эффект.Частично, чтобы сохранить предыдущее поведение «влияет только на текущий файл», но, что более важно, разрешить библиотеке использовать
toto
в качестве динамически изменяемой переменной, не предотвращая использование другими библиотекамиtoto
в качестве лексически изменяемой переменной (обычно соглашение об именовании префиксов пакетов позволяет избежать конфликтует, но это не везде используется, к сожалению), новое поведение(defvar FOO)
было определено, чтобы применяться только к текущему файлу, и даже было уточнено, поэтому оно применяется только к текущей области (например, если оно появляется внутри функции, оно влияет только на использование что вар в этой функции).Принципиально
(defvar FOO VAL)
и(defvar FOO)
это просто две «совершенно разные» вещи. Они просто используют одно и то же ключевое слово по историческим причинам.источник
(defvar FOO)
делает новый режим намного более совместимым со старым кодом. Кроме того, проблема IIRC с решением CommonLisp заключается в том, что для чистого интерпретатора, подобного Elisp, это довольно дорого (например, каждый раз, когда вы оцениваете,let
вы должны заглянуть внутрь его тела на случай, еслиdeclare
что-то повлияет на некоторые переменные).Основываясь на экспериментах, я считаю, что проблема заключается в том, что
(defvar VAR)
отсутствие значения init влияет только на библиотеки, в которых оно появляется.Когда я добавил
(defvar my-dynamic-var)
в*scratch*
буфер, ошибка больше не возникала.Первоначально я думал, что это из-за оценки этой формы, но затем я заметил, во-первых, что достаточно просто посетить файл с этой формой; и, кроме того, простого добавления (или удаления) этой формы в буфере без оценки этого было достаточно, чтобы изменить то, что произошло при оценке
(let ((my-dynamic-var 5)) (f2))
внутри этого же буфера с помощьюeval-last-sexp
.(У меня нет реального понимания того, что здесь происходит. Я нахожу поведение удивительным, но я не знаком с деталями того, как эта функциональность реализована.)
Я добавлю, что эта форма
defvar
(без значения init) не позволяет байт-компилятору жаловаться на использование внешней определенной динамической переменной в компилируемом файле elisp, но сама по себе она не приводит к тому, что эта переменная будетboundp
; так что это не строго определение переменной. (Обратите внимание, что если бы переменная былаboundp
тогда, эта проблема не возникла бы вообще.)На практике я предполагаю , что это будет работать КИ при условии , что вы действительно включаете
(defvar my-dynamic-var)
в любой лексическом связывании библиотеки , которая использует вашmy-dynamic-var
переменную (которые , предположительно , будут иметь реальное определение в другом месте).Редактировать:
Благодаря указателю из @npostavs в комментариях:
И то,
eval-last-sexp
и другоеeval-defun
используетсяeval-sexp-add-defvars
для того, чтобы:В частности , она находит все
defvar
,defconst
иdefcustom
экземпляры. (Даже когда закомментировано, я замечаю.)Поскольку это поиск в буфере во время вызова, он объясняет, как эти формы могут оказывать влияние в буфере, даже не будучи оцененным, и подтверждает, что форма должна появиться в том же файле elisp (а также раньше, чем оцениваемый код) ,
источник
eval-sexp-add-defvars
проверяет наличие defvars в тексте буфера.Я не могу воспроизвести это вообще, оценка последнего фрагмента работает отлично и возвращает 5, как и ожидалось. Вы уверены, что не оцениваете
my-dynamic-var
сами по себе? Это приведет к ошибке, поскольку переменная void, ей не присвоено значение, и она будет иметь только одно значение, если вы динамически связываете его с одним.источник
lexical-binding
не ноль перед оценкой форм? Я получаю поведение, которое вы описываете с помощьюlexical-binding
nil, но когда я устанавливаю его не равным nil, я получаю ошибку void-variable.lexical-binding
установлен и оценил формы последовательно.my-dynamic-var
динамическое значение верхнего уровня в текущем сеансе? Я думаю, что это может отметить это постоянно особенным.