Странное взаимодействие между лексической привязкой и defvar в emacs lisp
В следующем файле emacs lisp рассказывается о том, что происходит, когда Алиса использует локальную переменную с лексической привязкой. foo
в ее файле инициализации и Боб определяет foo
в качестве глобальной специальной переменной с defvar
в своем файле инициализации, и Алиса заимствует часть кода файла инициализации Боба в свой собственный файл инициализации, не зная, что foo
собирается стать особенным.
;; -*- lexical-binding: t; -*-
;; Alice init file
;; Alice defining alice-multiplier
(defun alice-multiplier-1 (foo)
(lambda (n) (* n foo)))
(defun alice-multiplier-2 (num)
(let ((foo num))
(lambda (n) (* n foo))))
;; Alice using alice-multiplier
(print
(list
:R1 (mapcar (alice-multiplier-1 10) (list 1 2 3))
:R2 (mapcar (alice-multiplier-2 10) (list 1 2 3))))
;; from Bob's code
;; ...
(defvar foo 1000)
;; ...
;; Alice using alice-multiplier
(print
(list
:R3 (mapcar (alice-multiplier-1 10) (list 1 2 3))
:R4 (mapcar (alice-multiplier-2 10) (list 1 2 3))))
Выход:
(:R1 (10 20 30) :R2 (10 20 30))
(:R3 (10 20 30) :R4 (1000 2000 3000))
Результаты R1 и R2 такие же, как я ожидаю. Результат R4 соответствует документации defvar, хотя он может удивить Алису, если она не прочитает код Боба.
Я нахожу R3 удивительным. Почему R3 такой?
Говоря о R4, что может сделать Алиса, чтобы защитить ее
foo
стать особенным для других? Например,foo
может быть лексической локальной переменной, которую она использует в своем файле инициализации или в одном из пакетов emacs, и(defvar foo "something")
может быть в некоторых из пакетов, которые она использует, илиfoo
может быть одним из новых специальных имен переменных, введенных в будущей версии Emacs. Есть ли что-то, что Алиса может добавить в свой файл, который говорит Emacs: "В этом файле foo всегда должен быть лексическим, даже если какой-то код извне использует специальную переменную с тем же именем"?
2 ответа
Что здесь происходит
С "теоретической" (Scheme/Common Lisp) точки зрения, как только вы включите лексические привязки, для всех практических целей alice-multiplier-1
а также alice-multiplier-2
идентичны Любая разница в их поведении является ошибкой в Emacs Lisp и должна быть сообщена как таковая.
скомпилированный
Если вы поставите свой код (т.е. 2 defun
с и ;; -*- lexical-binding: t; -*-
строка) в файл, emacs-list-byte-compile-and-load
это, тогда вы можете проверить мои претензии, оценив эти 4 формы:
(disassemble 'alice-multiplier-1)
(disassemble 'alice-multiplier-2)
(disassemble (alice-multiplier-1 10))
(disassemble (alice-multiplier-2 10))
вы увидите, что 3 и 4 идентичны, а 1 и 2 отличаются одной инструкцией (о которой следует сообщать сопровождающим Emacs как об ошибке, но это не влияет на поведение).
Обратите внимание, что ни одна из разборок не упоминает foo
, что означает, что defvar
не повлияет на их поведение.
Все хорошо!
Интерпретированный
Действительно, поведение, которое вы видите, неверно; правильный результат после defvar
является
(:R1 (10000 20000 30000) :R2 (10000 20000 30000))
пожалуйста, сообщите об этом сопровождающим Emacs.
Разные???
Да, defvar
влияет (и должно!) на поведение интерпретируемого кода и не влияет (и не должно!) на поведение скомпилированного кода.
Что ты должен делать
Там нет никакого способа "защитить" ваш foo
от провозглашенного special
другими - кроме префикса "ваши" символы с alice-
,
Однако, если вы скомпилируете файл с помощью alice-multiplier-1
определение, скомпилированный файл даже не содержит foo
и, следовательно, будущие декларации foo
не повлияет на вас.
Что касается второго вопроса, то, насколько я знаю, нет. Но все еще можно обойти. То есть принять два соглашения об именах: которые я назову желтым и зеленым.
соглашение об именовании желтого цвета
Все специальные переменные должны иметь желтые имена. Желтое имя - это имя, которое содержит хотя бы один дефис. Например hello-world
а также ga-na-da
желтые имена. Официальное руководство по Emacs Lisp и байтовый компилятор поддерживают это соглашение.
соглашение о присвоении зеленых названий
Зеленое имя - это имя, которое не является желтым. Например, helloworld
а также ganada
зеленые имена.
Все лексические нелокальные / свободные переменные должны иметь зеленые имена. Что такое нелокальная переменная? Тело анонимной функции внутри alice-multiplier-2
упоминает три имени, n
, foo
, num
, Из этих трех только n
имеет объявление в теле функции (анонимной функции). Два других являются нелокальными с точки зрения анонимной функции. Они являются нелокальными переменными.
Пока Алиса и Боб придерживаются двух соглашений об именах, все хорошо. Даже если они этого не делают сейчас, вполне вероятно, что эти люди, в конце концов, сойдутся с этими конвенциями самостоятельно, даже без взаимного общения, на следующих этапах:
Алиса и Боб не придерживаются ни одного из соглашений.
Так как руководство и байтовый компилятор поддерживают соглашение об именовании в желтом, наступает момент, когда Алиса и Боб начинают придерживаться соглашения о желтом.
Алиса принимает зеленое соглашение, которое, по крайней мере, защищает ее код от желтых специальных переменных другими.
Алиса и Боб придерживаются обоих соглашений.
Для точных условий, в которых могут возникнуть столкновения, см. Вторжение специальных переменных