Как жить с динамической областью видимости Emacs Lisp? - PullRequest
52 голосов
/ 24 сентября 2010

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

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

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

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

Ответы [ 10 ]

45 голосов
/ 24 сентября 2010

Не так уж и плохо.

Основные проблемы могут возникать со «свободными переменными» в функциях.

(defun foo (a)
  (* a b))

В приведенной выше функции aявляется локальной переменной.b является свободной переменной.В системе с динамическим связыванием, такой как Emacs Lisp, b будет проверяться во время выполнения.Теперь существует три случая:

  1. b не определено -> ошибка
  2. b - это локальная переменная, связанная с некоторым вызовом функции в текущей динамической области действия -> взятьэто значение
  3. b является глобальной переменной -> принять это значение

Тогда могут возникнуть следующие проблемы:

  • связанное значение (глобальное илилокальный) затеняется вызовом функции, возможно нежелательным
  • неопределенная переменная НЕ скрывается -> ошибка при доступе
  • глобальная переменная НЕ скрывается -> выбирает глобальное значение, которое можетбыть нежелательным

В Лиспе с компилятором, компиляция вышеуказанной функции может генерировать предупреждение о наличии свободной переменной.Обычно это делают компиляторы 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.См. Lexical Binding, GNU Emacs Lisp Справочное руководство .

13 голосов
/ 25 сентября 2010

В дополнение к последнему абзацу ответа Жиля, вот как RMS приводит аргументы в пользу динамического определения объема в расширяемой системе:

Некоторые дизайнеры языка считают, что следует избегать динамического связывания, и явная передача аргументов должна быть используется вместо Представьте себе, что функция А связывает переменную FOO и вызывает функция B, которая вызывает функцию C, а C использует значение FOO. Предположительно, А должен передать значение как аргумент B, который должен передать его в качестве аргумента С.

Это не может быть сделано в расширяемом Система, однако, потому что автор система не может знать, что все параметры будут. Представь, что функции A и C являются частью пользователя расширение, в то время как B является частью стандартная система. Переменная FOO делает не существует в стандартной системе; Это является частью расширения. Использовать явная передача аргументов будет требует добавления нового аргумента в B, что означает переписывание B и все что вызывает B. В наиболее распространенном случае, B - диспетчер команд редактора цикл, который вызывается из ужасного количество мест.

Что еще хуже, C также должен быть передан дополнительный аргумент. Б не относится к С по имени (С не существовало, когда Б было написано). Это, вероятно, находит указатель на C в командной рассылке Таблица. Это означает, что тот же вызов который иногда вызывает C мог бы одинаково хорошо вызвать любую команду редактора определение. Так что все редактирование Команды должны быть переписаны, чтобы принять и игнорировать дополнительный аргумент. От Теперь ни одна из оригинальной системы осталось!

Лично я думаю, что если есть проблема с Emacs-Lisp, то это не динамическая область видимости как таковая, а то, что она используется по умолчанию, и что невозможно достичь лексической области видимости без использования расширений. В CL могут использоваться как динамическая, так и лексическая область видимости, и, за исключением верхнего уровня (который реализуется несколькими реализациями deflex) и глобально объявленных специальных переменных, по умолчанию используется лексическая область видимости. В Clojure вы также можете использовать как лексическую, так и динамическую область видимости.

Чтобы снова процитировать RMS:

Не обязательно, чтобы динамическая область видимости была единственным предоставленным правилом области действия, она просто полезна чтобы это было доступно.

13 голосов
/ 24 сентября 2010

Есть ли несколько простых правил, которым я могу просто следовать и точно знать, что происходит с прицелом, не будучи укушенным каким-то странным, трудным для отладки способом? Чтение Справочник по Emacs Lisp , у вас будет много деталей, подобных этой:

  • Специальная форма: setq [символьная форма] ... Эта специальная форма является наиболее распространенным методом изменения значение переменной. Каждому символу присваивается новое значение, которое является Результат оценки соответствующей ФОРМЫ. Самый местный изменена существующая привязка символа .

Вот пример:

(defun foo () (setq tata "foo"))

(defun bar (tata) (setq tata "bar"))


(foo)
(message tata)
    ===> "foo"


(bar tata)
(message tata)
    ===> "foo"
11 голосов
/ 20 апреля 2013

Как отметил Питер Айтай:

Начиная с emacs-24.1, вы можете включить лексическую область видимости для каждого файла, поместив

;; -*- lexical-binding: t -*-

поверх файла elisp.

10 голосов
/ 24 сентября 2010

Во-первых, у elisp есть отдельные привязки переменных и функций, поэтому некоторые ловушки динамического определения области не актуальны.

Во-вторых, вы все равно можете использовать setq для установки переменных, но набор значений не сохраняетсявыход из динамической области видимости, в которой это делается. По сути, это не отличается от лексического определения области, с той лишь разницей, что при динамическом определении объема setq в функции, которую вы вызываете, может влиять на значение, которое вы видите после вызова функции.

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

Я бы сказал "напиши код как обычно".Бывают случаи, когда динамическая природа elisp вас кусает, но я обнаружил, что на практике это на удивление редко.

Вот пример того, что я говорил о setq и динамически связанных переменных (недавно оцененныхв ближайшем буфере scratch ):

(let ((a nil))
  (list (let ((a nil))
          (setq a 'value)
          a)
        a))

(value nil)
8 голосов
/ 21 августа 2011

Все, что здесь написано, имеет смысл.Я бы добавил это: узнайте Common Lisp - если ничего другого, прочитайте об этом.CLTL2 хорошо представляет лексическое и динамическое связывание, как и другие книги.И Common Lisp хорошо интегрирует их на одном языке.

Если вы «поймете это» после некоторого ознакомления с Common Lisp, то для Emacs Lisp вам будет понятнее.Emacs 24 по умолчанию использует лексическую область видимости в большей степени, чем более старые версии, но подход Common Lisp будет все еще более ясным и чистым (IMHO).Наконец, это определенно случай, когда динамическая область важна для Emacs Lisp, по причинам, которые RMS и другие подчеркнули.

Поэтому я предлагаю получитьузнать, как Common Lisp справляется с этим.Постарайтесь забыть о Scheme, если это ваша основная ментальная модель Lisp - она ​​ограничит вас больше, чем поможет вам понять область видимости, funargs и т. Д. В Emacs Lisp.Emacs Lisp, как и Common Lisp, "грязен и низок";это не схема.

5 голосов
/ 25 сентября 2010

Динамическая и лексическая область видимости ведут себя по-разному, когда фрагмент кода используется в другой области действия, отличной от той, в которой он был определен. На практике существуют два шаблона, которые охватывают большинство проблемных случаев:

  • Функция затеняет глобальную переменную, затем вызывает другую функцию, которая использует эту глобальную переменную.

    (defvar x 3)
    (defun foo ()
      x)
    (defun bar (x)
      (+ (foo) x))
    (bar 0) ⇒ 0
    

    Это не часто встречается в Emacs, потому что локальные переменные обычно имеют короткие имена (часто одиночные-word), тогда как глобальные переменные обычно имеют длинные имена (часто с префиксом 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- в качестве префикса даже для своих локальных переменных в функциях высшего порядка.На практике это работает достаточно хорошо, если вы позаботитесь о том, чтобы не использовать одну и ту же переменную как глобальное имя и как локальное имя, и вам не нужно писать рекурсивную функцию более высокого порядка.

PS Возраст Emacs Lisp - не единственная причина, по которой он динамически ограничен.Правда, в те дни шутки были склонны к динамическому определению - Scheme и Common Lisp действительно еще не взялись за дело.Но динамическая область видимости также является преимуществом языка, нацеленного на динамическое расширение системы: она позволяет подключаться к большему количеству мест без каких-либо особых усилий.С большой силой приходит великая веревка, чтобы повеситься: вы рискуете случайно попасть в место, о котором не знали.

4 голосов
/ 27 сентября 2010

Другие ответы хороши в объяснении технических деталей о том, как работать с динамическим определением области, поэтому вот мой нетехнический совет:

Просто сделайте это

Я работаю с Emacs lisp уже более 15 лет и не знаю, что меня когда-либо укусили какие-либо проблемы из-за различий в лексическом / динамическом объеме.

Лично я не нашел необходимости в замыканиях (я люблю их, просто они не нужны для Emacs).И, как правило, я стараюсь избегать глобальных переменных в целом (независимо от того, была ли область видимости лексической или динамической).

Поэтому я предлагаю перейти к написанию настроек, соответствующих вашим потребностям / желаниям,у вас не будет никаких проблем.

2 голосов
/ 04 сентября 2014

Только не надо.

Emacs-24 позволяет использовать лексическую область видимости. Просто беги

(setq lexical-binding t)

или добавить

;; -*- lexical-binding: t -*-

в начале вашего файла.

2 голосов
/ 24 сентября 2010

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

Хотя у меня нет больше советов по работе с отсутствующими функциями, которые предыдущие ответы еще не охватили, я хотел бы указать на существование ветки emacs под названием `lexbind ', реализующей лексическое связывание в обратно совместимый способ. По моему опыту, лексические замыкания все еще немного ошибочны в некоторых обстоятельствах, но эта ветвь кажется многообещающим подходом.

...