Как жить с динамическим охватом Emacs Lisp?
Я узнал Clojure раньше и очень люблю язык. Я также люблю Emacs и взломал некоторые простые вещи с Emacs Lisp. Есть одна вещь, которая мешает мне мысленно делать что-то более существенное с Elisp. Это концепция динамического охвата. Я просто боюсь этого, так как он так чуждо мне и пахнет полуглобальными переменными.
Итак, с объявлениями переменных я не знаю, какие вещи безопасны, а какие опасны. Из того, что я понял, переменные, заданные с помощью setq, попадают под динамическое масштабирование (верно?) Что относительно переменных? Где-то я читал, что let позволяет вам делать лексическое определение, но где-то еще я читаю, что пусть vars также динамически охвачены.
Я больше всего беспокоюсь о том, что мой код (используя setq или let) случайно разбивает некоторые переменные из платформы или стороннего кода, который я вызываю, или после такого вызова мои локальные переменные случайно запутались. Как я могу избежать этого?
Есть ли несколько простых правил, которыми я могу просто следовать и точно знать, что происходит с областью, не будучи укушенным каким-то странным, трудно отлаживаемым способом?
Ответы
Ответ 1
Это не так уж плохо.
Основные проблемы могут возникать с "свободными переменными" в функциях.
(defun foo (a)
(* a b))
В приведенной выше функции a
есть локальная переменная. b
- свободная переменная. В системе с динамической привязкой, такой как Emacs Lisp, b
будет проверяться во время выполнения. В настоящее время существует три случая:
-
b
не определен → ошибка
-
b
- локальная переменная, связанная некоторым вызовом функции в текущей динамической области → принимает это значение
-
b
- глобальная переменная → принять это значение
Теперь проблемы могут быть следующими:
- связанное значение (глобальное или локальное) затеняется вызовом функции, возможно нежелательным
- переменная undefined НЕ затенена → ошибка при доступе
- глобальная переменная НЕ затенена → выбирает глобальное значение, которое может быть нежелательным
В Lisp с компилятором компиляция указанной функции может вызвать предупреждение о наличии свободной переменной. Обычно это будут компиляторы Common Lisp. Интерпретатор не будет предоставлять это предупреждение, как только он увидит эффект во время выполнения.
Совет
- убедитесь, что вы случайно не используете свободные переменные
- убедитесь, что глобальные переменные имеют специальное имя, поэтому их легко обнаружить в исходном коде, обычно
*foo-var*
Не пишите
(defun foo (a b)
...
(setq c (* a b)) ; where c is a free variable
...)
Запись:
(defun foo (a b)
...
(let ((c (* a b)))
...)
...)
Привяжите все переменные, которые хотите использовать, и вы хотите убедиться, что они не связаны где-то еще.
В основном это.
Так как лексическая привязка GNU Emacs версии 24 поддерживается в Emacs Lisp. См.: Лексическая привязка, Справочное руководство GNU Emacs Lisp.
Ответ 2
Есть ли несколько простых правил, которыми я могу просто следовать и точно знать, что происходит с областью, не будучи укушенным каким-то странным, трудно отлаживаемым способом?
Прочитайте Emacs Lisp Ссылка, у вас будет много деталей, подобных этому:
- Специальная форма: setq [символьная форма]... Эта специальная форма является наиболее распространенным методом изменения значение переменной. Каждому SYMBOL присваивается новое значение, которое является результат оценки соответствующей ФОРМЫ. Самый локальный существующая привязка символа изменяется.
Вот пример:
(defun foo () (setq tata "foo"))
(defun bar (tata) (setq tata "bar"))
(foo)
(message tata)
===> "foo"
(bar tata)
(message tata)
===> "foo"
Ответ 3
В дополнение к последнему абзацу ответа Жиля, вот как RMS утверждает в пользу динамического охвата в расширяемой системе:
Некоторые разработчики языка считают, что следует избегать динамического связывания; явная передача аргументов должна быть используется вместо этого. Представьте себе, что функция A связывает переменную FOO и вызывает функция B, вызывающая функцию C и C использует значение FOO. Предположительно, A должно передать значение как аргумент B, который должен пройти его как аргумент для C.
Это невозможно сделать в расширяемом системы, однако, поскольку автор система не может знать, что параметры будут. Представьте, что функции A и C являются частью пользователя расширение, в то время как B является частью стандартная система. Переменная FOO делает не существует в стандартной системе; Это является частью расширения. Использовать явная передача аргументов требуется добавить новый аргумент в B, что означает переписывание B и все который называет B. В наиболее распространенном случае, B - диспетчер команд редактора петля, вызываемая из ужасного количество мест.
Что еще хуже, C также должен быть передан дополнительный аргумент. B не ссылается на C по имени (C не существовало, когда B было написано). Вероятно, он находит указатель на C в командной диспетчеризации Таблица. Это означает, что тот же звонок который иногда называет C вызов любой команды редактора определение. Итак, все редактирование команды должны быть перезаписаны, чтобы принять и игнорировать дополнительный аргумент. От теперь ни одна из исходных систем левый!
Лично я считаю, что если есть проблема с Emacs- Lisp, это не динамическое охват как таковое, а то, что это значение по умолчанию, и что невозможно достичь лексического охвата, не прибегая к расширениям. В CL можно использовать как динамическое, так и лексическое охват, и - кроме верхнего уровня (который называется несколькими реализациями deflex) и объявленных глобальными объявленными переменными - по умолчанию используется лексическое определение. В Clojure вы также можете использовать лексическое и динамическое масштабирование.
Чтобы снова указать RMS:
Нет необходимости, чтобы динамическая область была единственным предоставленным правилом области, просто полезно чтобы он был доступен.
Ответ 4
Как отметил Питер Айтай:
Так как emacs-24.1 вы можете включить лексическое масштабирование для каждого файла, поставив
;; -*- lexical-binding: t -*-
поверх вашего elisp файла.
Ответ 5
Во-первых, elisp имеет отдельные привязки переменных и функций, поэтому некоторые ошибки динамического охвата не имеют отношения.
Во-вторых, вы можете использовать setq
для установки переменных, но набор значений не выдерживает выхода из динамической области, в которой он выполняется. Это не принципиально отличается от лексического охвата, с той разницей, что с динамическим просмотром setq в вызываемой функции может повлиять на значение, которое вы видите после вызова функции.
Там lexical-let
, макрос, который (по существу) имитирует лексические привязки (я полагаю, что он делает это, прогуливаясь по телу и изменяя все вхождения лексически допустимых переменных в gensymmed name, в конце концов, не говоря уже о символе), если вы абсолютно необходимо.
Я бы сказал, "напишите код как обычно". Бывают случаи, когда динамический характер elisp укусит вас, но я обнаружил, что на практике это на удивление редко.
Вот пример того, что я говорил о setq и динамически связанных переменных (недавно оцененных в соседнем буфере с нуля):
(let ((a nil))
(list (let ((a nil))
(setq a 'value)
a)
a))
(value nil)
Ответ 6
Все, что было написано здесь, стоит того. Я бы добавил следующее: познакомиться с Common Lisp - если ничего другого, прочитайте об этом. CLTL2 хорошо описывает лексическую и динамическую привязку, как и другие книги. И Common Lisp хорошо интегрирует их на одном языке.
Если вы "получите" после некоторого воздействия Common Lisp, то для Emacs Lisp вам станет понятнее. Emacs 24 в большей степени использует лексическое охват по сравнению с более старыми версиями, но общий подход Lisp все еще будет более ясным и чистым (IMHO). Наконец, это определенно случай, когда динамическая область важна для Emacs Lisp по причинам, которые подчеркивают RMS и другие.
Итак, мое предложение - узнать, как общается с Общим Lisp. Попытайтесь забыть о Схеме, если это ваша главная ментальная модель Lisp - она ограничит вас чем-то, что поможет вам понять область видимости, funargs и т.д. В Emacs Lisp. Emacs Lisp, как Common Lisp, "грязный и низкий"; это не схема.
Ответ 7
Динамическое и лексическое охват имеют разные типы поведения, когда кусок кода используется в другой области действия, чем тот, на котором он был определен. На практике существует два шаблона, которые охватывают самые неприятные случаи:
-
Функция затеняет глобальную переменную, затем вызывает другую функцию, которая использует эту глобальную переменную.
(defvar x 3)
(defun foo ()
x)
(defun bar (x)
(+ (foo) x))
(bar 0) ⇒ 0
Это часто не возникает в Emacs, потому что локальные переменные имеют короткие имена (часто однословные), тогда как глобальные переменные имеют длинные имена (часто префикс packagename-
). Многие стандартные функции имеют имена, заманчивые для использования в качестве локальных переменных, таких как list
и point
, но функции и переменные, находящиеся в отдельных пространствах имен, являются локальными функциями, которые не используются очень часто.
-
Функция определена в одном лексическом контексте и используется вне этого лексического контекста, поскольку она передается функции более высокого порядка.
(let ((cl-y 10))
(mapcar* (lambda (elt) (* cl-y elt)) '(1 2 3)))
⇒ (10 20 30)
(let ((cl-x 10))
(mapcar* (lambda (elt) (* cl-x elt)) '(1 2 3)))
⇑ (wrong-type-argument number-or-marker-p (1 2 3))
Ошибка связана с использованием cl-x
в качестве имени переменной в mapcar*
(из пакета cl
). Обратите внимание, что пакет cl
использует cl-
в качестве префикса даже для своих локальных переменных в функциях более высокого порядка. Это на практике работает достаточно хорошо, если вы не станете использовать ту же переменную, что и глобальное имя, и как локальное имя, и вам не нужно писать рекурсивную функцию более высокого порядка.
P.S. Emacs Lisp возраст - не единственная причина, почему он динамически охвачен. Правда, в те дни лиспы имели тенденцию к динамическому охвату - Scheme и Common Lisp пока не были приняты. Но динамическое масштабирование также является преимуществом на языке, направленном на динамическое расширение системы: это позволяет вам захватывать больше мест без особых усилий. С большой силой приходит великая веревка, чтобы повесить себя: вы рискуете случайно подключиться к месту, о котором вы не знали.
Ответ 8
Другие ответы хорошо объясняют технические подробности о том, как работать с динамическим охватом, поэтому здесь нетехнические советы:
Просто сделайте это
Я занимаюсь Emacs lisp в течение 15 лет и не знаю, что меня когда-либо укусили какие-либо проблемы из-за различий между лексической/динамической областью.
Лично я не нашел необходимости в закрытии (я люблю их, просто не нуждаюсь в них для Emacs). И, как правило, я стараюсь избегать глобальных переменных в целом (было ли поле обзора лексическим или динамическим).
Поэтому я предлагаю подпрыгивать и писать настройки, которые соответствуют вашим потребностям/желаниям, скорее всего, у вас не будет никаких проблем.
Ответ 9
Я полностью чувствую твою боль. Я считаю, что недостаток лексической привязки в emacs довольно раздражает - особенно не имея возможности использовать лексические замыкания, которые, кажется, являются решением, которое я думаю о многом, исходя из более современных языков.
В то время как у меня нет больше советов по работе с отсутствующими функциями, которые ранее не были затронуты предыдущими ответами, я хотел бы указать на существование ветки emacs под названием `lexbind ', реализующую лексическую привязку в обратный подход. По моему мнению, лексические замыкания в некоторых случаях по-прежнему немного ошибочны, но эта ветвь представляется перспективным.
Ответ 10
Просто не делай.
Emacs-24 позволяет использовать лексическую область. Просто запустите
(setq lexical-binding t)
или добавить
;; -*- lexical-binding: t -*-
в начале файла.