Что происходит?
Вы говорите: чувствую, что здесь ничего особенного не происходит. Внешний foo
в bar
увеличивает глобальный x
, а foo
в окружении let
в bar
увеличивает затененный x
. Что в этом такого?
Особое , которое здесь происходит, заключается в том, что LET
может скрывать значение *x*
. С лексическими переменными это невозможно.
Код объявляет *x*
специальным через DEFVAR
.
В FOO
теперь значение *x*
ищется динамически. FOO
примет текущее динамическое связывание из *x*
или, если его нет, значение символа *x*
. Новое динамическое связывание может, например, быть введено с LET
.
С другой стороны, лексическая переменная должна присутствовать где-то в лексической среде. LET
, LAMBDA
, DEFUN
и другие могут вводить такие лексические переменные. Смотрите здесь лексическую переменную x
, введенную тремя различными способами:
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
Если бы наш код был:
(defvar x 0)
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
Тогда X
были специальными во всех трех вышеупомянутых случаях из-за объявления DEFVAR
, в котором X
объявляется специальным - глобально для всех уровней. Из-за этого существует соглашение об объявлении специальных переменных как *X*
. Таким образом, только переменные со звездами вокруг них особые - согласно соглашения . Это полезное соглашение.
В вашем коде у вас есть:
(defun bar ()
(foo)
(let ((*x* 20))
(foo))
(foo))
Так как *x*
было объявлено special через DEFVAR
выше в вашем коде, конструкция LET
вводит новое динамическое связывание для *x*
. FOO
затем вызывается. Поскольку внутри FOO
*x*
используется динамическое связывание , он ищет текущее и обнаруживает, что *x*
динамически связан с 20
.
Значение переменной special найдено в текущей динамической привязке.
Местные СПЕЦИАЛЬНЫЕ декларации
Существуют также локальные special
объявления:
(defun foo-s ()
(declare (special *x*))
(+ *x* 1))
Если переменная была объявлена special с помощью DEFVAR
или DEFPARAMETER
, то локальное объявление special
может быть опущено.
Лексическая переменная напрямую ссылается на привязку переменной:
(defun foo-l (x)
(+ x 1))
Давайте посмотрим на практике:
(let ((f (let ((x 10))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(print (funcall f))))
Здесь все переменные лексические. В форме 2 LET
не будет затенять X
в нашей функции f
. Не может Функция использует лексическую связанную переменную, введенную LET ((X 10)
. Окружение вызова другим лексически связанным X
в форме 2 не влияет на нашу функцию.
Давайте попробуем специальные переменные:
(let ((f (let ((x 10))
(declare (special x))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(declare (special x))
(print (funcall f))))
Что теперь? Это работает?
Это не так!
Первая форма вызывает функцию и пытается найти динамическое значение X
, а ее нет. Мы получаем сообщение об ошибке в форма 1 : X
несвязано, поскольку в действительности отсутствует динамическое связывание.
Форма 2 будет работать, так как LET
с объявлением special
вводит динамическое связывание для X
.