Регистрация | Войти
Lisp — программируемый язык программирования

Переменные в Common Lisp

Автор: Иван Болдырев

Источник: http://lispnik.livejournal.com/9137.html

Многим изучающим язык программирования Common Lisp не до конца ясно различие между лексическими и динамическими переменными. Я решил написать небольшую статью, в которой сделана попытка объяснить это различие.

Сделаю важное замечание: в CL любая переменная должна быть тем или иным способом объявлена: с помощью defvar/defparameter или с помощью let. В стандарте ANSI указано, что последствия использования необъявленных переменных не определены, то есть конкретная реализация может делать в этом случае всё, что захочет: может выдать сообщение об ошибке, может молча объявить эту переменную как лексическую или (более вероятно) как динамическую. Так что примеры в двухтомнике Хювеннена-Сеппянена и в книге Пола Грэхэма ANSI Common Lisp, в которых в командной строке с помощью setf/setq присваиваются значения необъявленным переменным, не корректны. Однако извинением авторам служит то, что книги были вышли до принятия окончательной версии стандарта ANSI Common Lisp, в котором это было зафиксировано.

Самые первые версии Лиспа имели только динамические переменные. Насколько мне известно, первым диалектом Лиспа с лексическими переменными была Схема (Scheme). В Common Lisp переменные по умолчанию являются лексическими, а динамические переменные должны быть соответствующим образом объявлены.

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

  1. Указать её имя в defvar или defparameter:
    (defvar *special-var1* nil)
    (defparameter *special-var2* nil)
  2. Указать её имя в декларации special.
    (let ((*test* nil))
       (declare (special *test*))
       (do-something-special)
    )


    ;;; Или даже так:
    (defun something-special2 (lex-var1 *test* lex-var2)
      (declare (special *test* *test2*))
      (do-something-special lex-var1 *test* *test2* lex-var2)
    )

Все остальные переменные будут лексическими.

Имена динамических переменных обрамляются звёздочками по традиции, чтобы можно по имени переменной понять, является ли она динамической.

А теперь рассмотрим различия в поведении лексических и динамических переменных.

Главное различие — область видимости. Лексическая переменная видна только в теле той формы, в которой она была объявлена (в теле функции в случае defun, в теле let в случае let, и т.д.). Динамическая переменная, даже если она была создана локально с помощью let, видна во всех вызываемых функциях пока действительно связывание с помощью let:

(defun test1 (a)
  (declare (special *somevar*))
  (+ a *somevar*)
)


(defun test2 (b)
  (let ((*somevar 10) (anothervar 20))
    (declare (special *somevar*))
    (test1 (1+ b))
)
)

В данном примере значение переменная *somevar*, которое было ей присвоено в tes2, видно и в функции test1, а вот значение лексической переменной anothervar в test1 нельзя узнать вообще никак.

Интересным является то, что связывания динамических переменных, созданные с помощью let (а так же с другими формами, осуществляющими связывание, например with-open-file или defun и lambda, создающие связывания для своих аргументов) влияют на все вложенные вызовы функций (если они, конечно, не сами не связывают те же переменные). После выхода из let восстанавливается предыдущее связывание, если оно было. Вот как это можно использовать: допустим, у нас есть функция funny-print, которая что-то печатает на экране, используя стандартную динамическую переменную *output-stream*. Используя let, мы можем "подменить" её значение, заставив функцию печатать в файл или строку:

(with-output-to-string (*output-stream*)
  (funny-print some-object)
)

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

Можно считать, что с каждой динамической переменной связан специальный стек связываний. Вычисления и присваивания (setf/setq/set) работают со связыванием, которое расположено на вершине стека. Каждая форма let (и эквивалентные ей), в которой указана динамическая переменная, в начале помещает на стек новое связывание и удаляет его в конце выполнения. При этом все манипуляции, которые выполнялись над этим связыванием, влияют только на это связывание, поэтому иногда можно увидеть код, подобный следующему:

(let ((*somevar* *somevar*))
  ;; вычисления, которые могут модифицировать *somevar*, но мы этого не хотим
)

После выполнения этой формы старое значение *somevar* останется неизменным, что бы не происходило внутри этой формы.

Лексические и динамические переменные ведут себя по-разному в замыканиях. А именно, связывания лексических переменных запоминаются при создании замыкания (как говорят, сохраняется лексическое окружение), а связывания динамических переменных — нет, при каждом вызове замыкания значения берутся из вызываемого окружения. Вот простой пример:

(defvar *shift1* 100)  ; динамическая переменная, которую мы
                     ; будем использовать в замыкании

;;; Создаём замыкание, в котором лексический контекст состоит
;;; из переменной shift2.  Отметим, что в теле лямбда-выражения
;;; переменная x также является лексической

(defvar *test-closure*    ; тоже динамическая, но это не важно
(let ((shift2 20))
    (lambda (x) (+ x *shift1* shift2))
)
)


;;; Пробуем
(funcall *test-closure* 3)
=> 123

;;; Создаём новое связывание *shift1*:
(let ((*shift1* 400))
  (funcall *test-closure* 3)
)

=> 423

;;; Создаём новое связывание shift2:
(let ((shift2 40))
  (funcall *test-closure* 3)
)

=> 123

Как видим, в последнем примере результат не изменился!

Все глобальные переменные в Common Lisp являются динамическими! Глобальных лексических переменных в Common Lisp нет, хотя их можно имитировать с помощью макросимволов.

Если же нужно запомнить в замыкании значение динамической переменной во время создания замыкания (значение, а не связывание, которое запомнить невозможно), то можно воспользоваться временной лексической переменной:Как видим, в последнем примере результат не изменился!

(let ((temp-lexical *dinamyc-var*)) ; запоминаем
(lambda (x)
    (let ((*dynamyc-var* temp-lexical)) ; восстанавливаем
    (some-fun-that-uses-dinamyc-var x)
)
)
)

И последнее отличие, которое я упомяну, заключается в том, что динамическая переменная может не иметь значения, в то время как у лексической переменной всегда есть какое-то значение. Это происходит тогда, когда переменная объявляется динамической с помощью defvar или декларации special, но никакое значение ей не присваивается.

@2009-2013 lisper.ru