Избавьтесь от ссылок на предупреждения о свободной компиляции переменных

Я пишу основной режим emacs, который использует локальные переменные буфера для хранения некоторого состояния:

(defun foo-mode ()
  "My nice major mode"
  (interactive)
  (kill-all-local-variables)
  (setq mode-name "foo")
  (setq major-mode 'foo-mode)
  (set (make-local-variable 'foo-state) "bar"))

(defun foo-change-state ()
  (setq foo-state "baz"))

Это работает очень хорошо и имеет то свойство, что в любом буфере, не использующем мой основной режим, переменная foo-state не привязана (что, на мой взгляд, хорошо, поскольку позволяет избежать загромождения таблицы символов).

Однако байтовая компиляция такого фрагмента кода выдает следующее предупреждение:

Warning: assignment to free variable `foo-state'

Использование defvar избавляет от предупреждения, но имеет побочный эффект, заключающийся в том, что foo-state теперь привязано везде, что, на мой взгляд, нежелательно.

Есть ли способ избавиться от предупреждений, не привязывая при этом зависящие от режима переменные в каждом буфере? Или я ошибаюсь, когда думаю, что эти переменные не должны объявляться глобально?


person François Févotte    schedule 14.09.2012    source источник
comment
Что ж, это было особенно бесполезным преобразованием ответа в комментарий кем-то. Чтобы снова сделать приведенное выше читаемым: C-h i g (elisp) Warning Tips RET - это место, где живет документация, отвечающая на этот вопрос.   -  person phils    schedule 08.04.2014
comment
@филс Да. Мне, например, понравился твой ответ. Спасибо!   -  person François Févotte    schedule 08.04.2014
comment
Кстати, я рекомендую вам использовать define-derived-mode, как в (define-derived-mode foo-mode nil "foo" "My nice major mode." (set (make-local-variable 'foo-state) "bar")).   -  person Stefan    schedule 16.04.2014
comment
Остерегаться! Раздел Warning Tips в Руководстве по Emacs содержит вопиющую ложь относительно defvar: такое определение не имеет никакого эффекта, кроме указания компилятору не предупреждать об использовании переменной [...] в этом файле. Это, безусловно, может иметь большее влияние чем просто заставить компилятор замолчать: [он] также объявляет переменную как специальную, так что она всегда динамически связывается, даже если «лексическое связывание» равно t. (цитата взята из фактической документации для defvar). Возможно, совет в Warning Tips был написан до введения лексического связывания в Emacs Lisp...   -  person ack    schedule 01.03.2018


Ответы (2)


Официальный способ делать то, что вы хотите, это (defvar foo-state). Обратите внимание на отсутствие второго аргумента. Также обратите внимание, что такое объявление применяется только к файлу, в котором оно находится (или к области, в которой оно находится, если оно используется внутри функции).

person Stefan    schedule 15.09.2012
comment
Спасибо. Перечитывая документацию defvar, становится ясно: если INITVALUE отсутствует, значение SYMBOL не установлено. Я пропустил это и всегда предоставлял второй аргумент (из-за которого переменная всегда была привязана). - person François Févotte; 15.09.2012

Объявите переменную с помощью defvar. Нет другого способа удалить предупреждение, и это действительно считается хорошей практикой.

Ваше намерение не загромождать таблицу символов оправдано, но на самом деле вы этого не делаете. Я думаю, вы неправильно поняли семантику связывания переменных в Emacs Lisp, поскольку вы, кажется, полагаете, что, не объявляя его, foo-state будет несвязанным в любом буфере, не использующем foo-mode. Это не так.

В Emacs Lisp имена (они же символы) являются глобальными. Как только foo-state оценивается в первый раз, среда выполнения создает новый объект символа для foo-state и помещает его в глобальную таблицу символов (также известную как obarray). Локальных таблиц символов нет, поэтому не имеет значения, где и как оценивается foo-state, foo-state ссылается на один и тот же объект символа в любом месте (см. Создание символов).

Каждый объект символа состоит из компонентов (ячеек), одним из которых является ячейка переменная (см. Компоненты символов). setq изменяет текущую привязку системы, на верхнем уровне без лексической привязки это эффективно изменяет ячейку переменной объекта символа, таким образом, глобальное значение переменной. Опять же, не имеет значения, где оценивается setq. На самом деле, если бы какое-то bar-mode оценивало (setq foo-state "bar"), foo-state тоже было бы привязано к "бару" в foo-mode, и наоборот.

Таким образом, единственный эффект (defvar) по сравнению с (setq) состоит в том, что документирует намерение использовать символ как глобальную переменную, то есть говорит другим не изменять эту переменную, если только не предполагается манипулирование поведением foo-mode. Вы можете прикрепить документацию к переменной и пометить ее как определенную в своем буфере (C-h v foo-state предоставит ссылку для перехода к определению).

Поскольку Emacs Lisp не имеет пространств имен и по умолчанию имеет динамическую область действия, документация крайне важна, чтобы избежать конфликтов между модулями. Если бы я написал bar-mode, используя ваш foo-mode, я мог бы случайно привязаться к foo-state, вызвать foo-change-state, а затем увидеть, что мой режим ведет себя неправильно, потому что переменная была непреднамеренно перезаписана. Объявление foo-state не делает это невозможным, но, по крайней мере, позволяет мне поймать ошибку, потому что C-h v foo-state покажет, что эта переменная используется другим режимом, поэтому мне лучше не использовать ее, если я действительно не собираюсь манипулировать этим режимом.

В качестве последнего слова: во всем вышеупомянутом тексте «режим» можно заменить файлами Emacs Lisp. modes ничего особенного в отношении символов. Все вышесказанное справедливо и для Emacs Lisp, который не объявляет режимы, а просто содержит набор функций.

person lunaryorn    schedule 14.09.2012
comment
Неправда, что foo-state не будет развязан в других буферах — просто попробуйте пример автора. В то время как массив является глобальным, значения локальных символов буфера изменяются при каждом переключении буфера, что может включать изменение состояния на несвязанное. Если вы нажмете M-: foo-state RET в буфере foo-mode, вы получите "bar", а если вы сделаете то же самое в другом буфере, вы получите ошибку. Хотя это не идиоматично в elisp, его, безусловно, можно получить независимо от глобальной таблицы символов. - person user4815162342; 15.09.2012
comment
Также обратите внимание, что, поскольку в Emacs Lisp отсутствует модульная система, не документация защищает вас от случайных конфликтов глобальных переменных, а соглашение об использовании префиксов символов. Вот почему каждый режим ставит перед всеми своими символами префикс mode-. defvar просто позволяет компилятору выдавать (точнее, не выдавать) предупреждение, которое улавливает ошибочные присваивания, то же самое предупреждение, которое является ложным срабатыванием в случае автора. - person user4815162342; 15.09.2012
comment
Если foo-state устанавливается другим буфером до того, как foo-mode объявляет его локальным буфером, он получает глобальное значение. Только после того, как переменная объявлена ​​локальной для буфера, вступают в силу привязки к локальному буферу. И даже тогда глобальную привязку все еще можно изменить через setq-default, хотя это, конечно, не повлияет на локальные привязки буфера. - person lunaryorn; 15.09.2012
comment
Префиксы символов помогают избежать конфликтов, но не предотвращают их, тем более, что особенно переменные, объявленные режимами программирования, часто имеют вездесущие имена просто потому, что префикс символа (то есть имя языка программирования) является вездесущим. Например, ruby-mode может объявить переменную ruby-executable для оценки буфера через интерпретатор ruby, но любая другая библиотека, работающая с Ruby (например, средство проверки flymake для Ruby), также может легко использовать эту переменную, потому что это наиболее естественное имя для выбора. это. Вот почему документация также необходима… - person lunaryorn; 15.09.2012
comment
Короче говоря, означает ли это, что в elisp все — просто глобальные переменные? Изменение одного места приведет к тому, что все последующие выполнения будут использовать новое значение? Очень красивое объяснение! - person Hot.PxL; 24.05.2015