Локальные переменные
Глобальные переменные имеют значения, которые сохраняются, пока их явно не заменят новыми значениями. Иногда полезно задать переменной локальное значение — значение, действующее только в определённой части программы на 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 ofy. (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