Spec-Zone.ru › Elisp

Локальные переменные

Глобальные переменные имеют значения, которые сохраняются, пока их явно не заменят новыми значениями. Иногда полезно задать переменной локальное значение — значение, действующее только в определённой части программы на Lisp. Когда переменная имеет локальное значение, говорят, что она локально связана с этим значением и является локальной переменной.

Например, при вызове функции её переменные-аргументы получают локальные значения — фактические аргументы, переданные при вызове функции; эти локальные привязки действуют в теле функции. Другой пример: специальная форма let явно устанавливает локальные привязки для определённых переменных, которые действуют только в теле формы let.

Также говорят о глобальной привязке, в которой (концептуально) хранится глобальное значение.

Создание локальной привязки сохраняет предыдущее значение переменной (или отсутствие значения). Говорят, что предыдущее значение затенено. Затеняться могут как глобальные, так и локальные значения. Если действует локальная привязка, использование setq для локальной переменной сохраняет указанное значение в локальной привязке. Когда эта локальная привязка перестаёт действовать, восстанавливается ранее затенённое значение (или его отсутствие).

У переменной одновременно может быть несколько локальных привязок (например, если вложенные формы let связывают эту переменную). Текущая привязка — это локальная привязка, которая действует в данный момент. Она определяет значение, возвращаемое при вычислении символа переменной, и именно к ней применяется setq.

В большинстве случаев текущую привязку можно считать самой внутренней локальной привязкой или глобальной привязкой, если локальной привязки нет. Точнее говоря, правило, называемое правилом области видимости, определяет, в какой части программы действует локальная привязка. Правило области видимости, используемое в Emacs Lisp по умолчанию, называется динамической областью видимости. Оно просто устанавливает, что текущей привязкой в любой момент выполнения программы является самая недавно созданная привязка этой переменной, которая всё ещё существует. Подробности о динамической области видимости и альтернативном правиле, называемом лексической областью видимости, см. в разделе Правила области видимости привязок переменных. В последнее время Emacs всё чаще переходит к использованию лексических привязок, стремясь в конечном итоге сделать их правилом по умолчанию. В частности, во всех исходных файлах Emacs Lisp и в буфере *scratch* используется лексическая область видимости.

Для создания локальных привязок существуют специальные формы let и let*:

Специальная форма: let (bindings…) forms…

Эта специальная форма создаёт локальные привязки для набора переменных, заданного параметром bindings, а затем вычисляет все forms в текстовом порядке. Она возвращает значение последней формы из forms. Локальные привязки, созданные с помощью let, действуют только в теле forms.

Каждый элемент bindings — это либо (i) символ, в этом случае символ локально связывается с nil; либо (ii) список вида (symbol value-form), в этом случае symbol локально связывается с результатом вычисления value-form. Если value-form не указан, используется nil.

Все выражения value-form в bindings вычисляются в порядке их появления и до привязки к ним каких-либо символов. Вот пример: z связывается со старым значением y, равным 2, а не с новым значением y, равным 1.

(setq y 2)
     ⇒ 2
(let ((y 1)
      (z y))
  (list y z))
     ⇒ (1 2)

Однако порядок привязок не определён: в следующем примере может быть выведено как 1, так и 2.

(let ((x 1)
      (x 2))
  (print x))

Поэтому не следует привязывать переменную более одного раза в одной форме let.

Специальная форма: let* (bindings…) forms…

Эта специальная форма похожа на let, но связывает каждую переменную сразу после вычисления её локального значения и до вычисления локального значения следующей переменной. Поэтому выражение в bindings может ссылаться на предшествующие символы, связанные в этой форме let*. Сравните следующий пример с приведённым выше примером для let.

(setq y 2)
     ⇒ 2
(let* ((y 1)
       (z y))    ; Use the just-established value of y.
  (list y z))
     ⇒ (1 1)

По сути, let* привязка x и y в предыдущем примере эквивалентна использованию вложенных привязок let:

(let ((y 1))
  (let ((z y))
    (list y z)))
Макрос: letrec (bindings…) forms…

Эта специальная форма похожа на let*, но все переменные связываются до вычисления каких-либо локальных значений. Затем значения присваиваются локально связанным переменным. Это полезно только при действующей лексической области видимости, когда нужно создать замыкания, ссылающиеся на привязки, которые при использовании let* ещё не действовали бы.

Например, вот замыкание, которое удаляет себя из обработчика после однократного выполнения:

(letrec ((hookfun (lambda ()
                    (message "Run once")
                    (remove-hook 'post-command-hook hookfun))))
  (add-hook 'post-command-hook hookfun))
Макрос: dlet (bindings…) forms…

Эта специальная форма похожа на let, но динамически связывает все переменные. Это редко бывает полезно: обычно обычные переменные следует связывать лексически, а специальные переменные (то есть переменные, определённые с помощью defvar) — динамически; именно так и поступает let.

dlet может пригодиться при взаимодействии со старым кодом, который предполагает динамическую привязку определённых переменных (см. раздел Динамическая привязка), но объявить эти переменные специальными с помощью defvar непрактично. dlet временно сделает связанные переменные специальными, выполнит формы, а затем снова сделает переменные неспециальными.

Макрос: named-let name bindings &rest body

Эта специальная форма представляет собой конструкцию цикла, вдохновлённую языком Scheme. Она похожа на let: связывает переменные из bindings, а затем вычисляет body. Однако named-let также связывает name с локальной функцией, формальными аргументами которой являются переменные из bindings, а телом — body. Благодаря этому body может рекурсивно вызывать себя, вызывая name; переданные name аргументы используются как новые значения связанных переменных в рекурсивном вызове.

Пример цикла для суммирования списка чисел:

(named-let sum ((numbers '(1 2 3 4))
                (running-sum 0))
  (if numbers
      (sum (cdr numbers) (+ running-sum (car numbers)))
    running-sum))
⇒ 10

Рекурсивные вызовы name, находящиеся в хвостовых позициях в body, гарантированно оптимизируются как хвостовые вызовы, то есть не занимают дополнительное пространство стека независимо от глубины рекурсии. Такие рекурсивные вызовы фактически переходят в начало цикла с новыми значениями переменных.

Вызов функции находится в хвостовой позиции, если это последнее выполняемое действие, так что возвращаемое вызовом значение является значением самого body, как в приведённом выше рекурсивном вызове sum.

named-let можно использовать только при включённой лексической области видимости. См. раздел Лексическая область видимости.

Ниже приведён полный список других средств, создающих локальные привязки:

  • Вызовы функций (см. раздел Функции).
  • Вызовы макросов (см. раздел Макросы).
  • condition-case (см. раздел Ошибки).

У переменных также могут быть привязки, локальные для буфера (см. раздел Переменные, локальные для буфера); у некоторых переменных есть привязки, локальные для терминала (см. раздел Несколько терминалов). Эти виды привязок в некоторой степени похожи на обычные локальные привязки, но их локальность определяется текущим местом в Emacs.

Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Local-Variables.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API