12.10.2 Динамическое связывание

Локальные привязки переменных являются динамическими в современном диалекте Lisp для специальных переменных (см. ниже) и для всех переменных в старом диалекте Lisp. (См. Выбор диалекта Lisp.) Динамические привязки переменных имеют свои преимущества, но в целом они более склонны к ошибкам и менее эффективны, чем лексические привязки, и компилятору сложнее находить ошибки в коде, использующем динамические привязки.

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

Динамические привязки имеют динамическую область видимости и протяженность, как показано в следующем примере:

(defvar x -99)  ; x получает начальное значение −99.

(defun getx ()
  x)            ; x используется свободно в этой функции.

(let ((x 1))    ; x динамически привязан.
  (getx))
     ⇒ 1

;; После завершения формы let x возвращается к своему
;; предыдущему значению, которое равно −99.

(getx)
     ⇒ -99

Функция getx ссылается на x. Это свободная ссылка, в том смысле, что в самой конструкции defun нет привязки для x. Когда мы вызываем getx из внутри формы let, в которой x (динамически) привязан, она извлекает локальное значение (т.е. 1). Но когда мы вызываем getx вне формы let, она извлекает глобальное значение (т.е. −99).

Вот еще один пример, иллюстрирующий установку динамически привязанной переменной с помощью setq:

(defvar x -99)      ; x получает начальное значение −99.

(defun addx ()
  (setq x (1+ x)))  ; Прибавляет 1 к x и возвращает его новое значение.

(let ((x 1))
  (addx)
  (addx))
     ⇒ 3           ; Два вызова addx прибавляют к x дважды.

;; После завершения формы let x возвращается к своему
;; предыдущему значению, которое равно −99.

(addx)
     ⇒ -98

Даже когда включено лексическое связывание, некоторые переменные продолжают быть динамически привязанными. Они называются специальными переменными. Каждая переменная, определенная с помощью defvar, defcustom или defconst, является специальной переменной (см. Определение глобальных переменных). Все остальные переменные подпадают под лексическое связывание.

Используя defvar без значения, возможно динамически привязать переменную только в одном файле или только в одной части файла, при этом оставив ее лексически привязанной в других местах. Например:

(let (_)
  (defvar x)      ; Привязки x будут динамическими внутри этого let.
  (let ((x -99))  ; Это динамическая привязка x.
    (defun get-dynamic-x ()
      x)))

(let ((x 'lexical)) ; Это лексическая привязка x.
  (defun get-lexical-x ()
    x))

(let (_)
  (defvar x)
  (let ((x 'dynamic))
    (list (get-lexical-x)
          (get-dynamic-x))))
    ⇒ (lexical dynamic)
Функция: special-variable-p symbol

Эта функция возвращает не-nil, если symbol является специальной переменной (т.е. имеет определение переменной defvar, defcustom или defconst). В противном случае возвращаемое значение равно nil.

Обратите внимание, что поскольку это функция, она может возвращать не-nil только для переменных, которые являются постоянно специальными, а не для тех, которые специальны только в текущей лексической области видимости.

Использование специальной переменной в качестве формального аргумента функции не поддерживается.

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