Как жить с динамическим охватом Emacs Lisp? - программирование
Подтвердить что ты не робот

Как жить с динамическим охватом Emacs Lisp?

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

Итак, с объявлениями переменных я не знаю, какие вещи безопасны, а какие опасны. Из того, что я понял, переменные, заданные с помощью setq, попадают под динамическое масштабирование (верно?) Что относительно переменных? Где-то я читал, что let позволяет вам делать лексическое определение, но где-то еще я читаю, что пусть vars также динамически охвачены.

Я больше всего беспокоюсь о том, что мой код (используя setq или let) случайно разбивает некоторые переменные из платформы или стороннего кода, который я вызываю, или после такого вызова мои локальные переменные случайно запутались. Как я могу избежать этого?

Есть ли несколько простых правил, которыми я могу просто следовать и точно знать, что происходит с областью, не будучи укушенным каким-то странным, трудно отлаживаемым способом?

4b9b3361

Ответ 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 -*-

в начале файла.