Странное взаимодействие между лексической привязкой и 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, хотя он может удивить Алису, если она не прочитает код Боба.

  1. Я нахожу R3 удивительным. Почему R3 такой?

  2. Говоря о 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 имеет объявление в теле функции (анонимной функции). Два других являются нелокальными с точки зрения анонимной функции. Они являются нелокальными переменными.

Пока Алиса и Боб придерживаются двух соглашений об именах, все хорошо. Даже если они этого не делают сейчас, вполне вероятно, что эти люди, в конце концов, сойдутся с этими конвенциями самостоятельно, даже без взаимного общения, на следующих этапах:

  1. Алиса и Боб не придерживаются ни одного из соглашений.

  2. Так как руководство и байтовый компилятор поддерживают соглашение об именовании в желтом, наступает момент, когда Алиса и Боб начинают придерживаться соглашения о желтом.

  3. Алиса принимает зеленое соглашение, которое, по крайней мере, защищает ее код от желтых специальных переменных другими.

  4. Алиса и Боб придерживаются обоих соглашений.

Для точных условий, в которых могут возникнуть столкновения, см. Вторжение специальных переменных

Другие вопросы по тегам