Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
859 lines (713 sloc) 70.9 KB
\chapter{Переменные}
\label{ch:06}
\thispagestyle{empty}
Следующий базовый строительный блок, с которым нам нужно ознакомиться,~--
переменные. Common Lisp поддерживает два вида переменных: лексические и
динамические\pclfootnote{Динамические переменные также иногда называют \textit{специальными
переменными} по причинам, рассматриваемым далее в этой главе. Очень важно, чтобы вы
знали об этом синониме, так как некоторые люди (и реализации Lisp) используют один
термин, а другие~-- другой.}. Эти два типа переменных примерно соответствуют
<<локальным>> и <<глобальным>> переменным других языков. Однако это соответствие лишь
приблизительное. С~одной стороны, <<локальные>> переменные некоторых языков в
действительности гораздо ближе к динамическим переменным Common Lisp\pclfootnote{Ранние
реализации Lisp использовали динамические переменные в качестве локальных, по крайней
мере при интерпретации. Elisp, диалект Lisp, применяемый в Emacs, является в некоторой
мере устаревшим в этом отношении, продолжая поддерживать только динамические
переменные. Некоторые другие языки повторили этот путь от динамических к лексическим
переменным: например, локальные переменные в Perl являются динамическими, в то время как
my-переменные, введённые в Perl 5,~-- лексические. Python никогда не имел настоящих
динамических переменных, но ввёл настоящую лексическую область видимости только с
версии~2.2. (Лексические переменные Python все ещё ограничены в сравнении с Lisp из-за
объединения присваивания (assignment) и связывания (binding) в синтаксисе языка.)}. А с
другой~-- локальные переменные некоторых других языков имеют \textit{лексическую область
видимости}, не предоставляя всех возможностей, имеющихся у лексических переменных
Common Lisp. В~частности, не все языки, предоставляющие переменные, имеющие лексическую
область видимости, поддерживают замыкания.
Чтобы сделать все ещё более запутанным, многие формы, которые работают с переменными,
могут использоваться как с лексическими, так и с динамическими переменными. Поэтому я
начну с обсуждения некоторых аспектов переменных Lisp, которые применимы к обоим видам
переменных, а затем рассмотрю специфические харак\-те\-рис\-ти\-ки лексических и динамических
переменных. Далее я проанализирую оператор присваивания общего назначения Common Lisp~--
\lstinline{SETF}, который используется для присваивания новых значений переменным, а также
почти любым местам, которые могут хранить значения.
\section{Основы переменных}
Как и в других языках, в Common Lisp переменные являются именованными мес\-тами (places),
которые могут содержать значения. Однако в Common Lisp переменные не типизированы таким же
образом, как в таких языках, как Java или C++. То есть вам не нужно описывать тип
объектов, которые может содержать каждая переменная. Вместо этого переменная может
содержать значения любого типа, и сами значения содержат информацию о типе, которая может
быть использована для проверки типов во время выполнения. Таким образом, Common Lisp
является \textit{динамически типизированным}: ошибки типов выявляются
динамически. Например, если вы передадите не число в функцию~\lstinline{+}, Common Lisp
сообщит вам об ошибке типов. С другой стороны, Common Lisp является \textit{строго
типизированным} языком в том смысле, что все ошибки типов будут обнаружены: нет способа
представить объект в качестве экземпляра класса, которым он не является\pclfootnote
действительности не совсем корректно говорить о том, что все ошибки типов всегда будут
обнаружены: существует возможность использования опциональных деклараций для указания
компилятору того, что определённые переменные всегда будут содержать объекты
определённого типа, и для отключения проверок типов во время выполнения определённых
участков кода.}.
Все значения в Common Lisp, по крайней мере концептуально, являются ссылками на
объекты\footnote{В~целях оптимизации определённые виды объектов, такие как целые числа,
меньшие определённого размера, и знаки могут быть представлены непосредственно в памяти,
другие же объекты будут представляться указателями на действительные объекты. Однако,
поскольку целые числа и знаки являются неизменяемыми значениями, не имеет значения тот
факт, что может существовать несколько копий <<одного и того же>> объекта в различных
переменных. Это и является корнем различия между \lstinline{EQ} и \lstinline{EQL}, описанного
в главе~\ref{ch:04}.}\hspace{\footnotenegspace}. Поэтому присваивание переменной нового значения изменяет то, на
\textit{какой} объект ссылается переменная (то есть куда ссылается переменная), но не
оказывает никакого влияния на объект, на который переменная ссылалась ранее. Однако,
если переменная содержит ссылку на изменяемый объект, вы можете использовать данную
ссылку для изменения этого объекта, и это изменение будет видимо любому коду, который
имеет ссылку на этот же объект.
Один из способов введения новой переменной вы уже использовали при определении параметров
функции. Как вы видели в предыдущей главе, при определении функции с помощью
\lstinline{DEFUN} список параметров определяет переменные, которые будут содержать аргументы,
переданные функции при вызове. Например, следующая функция определяет три переменные для
хранения своих аргументов: \lstinline{x}, \lstinline{y} и~\lstinline{z}.
\begin{myverb}
(defun foo (x y z) (+ x y z))
\end{myverb}
При каждом вызове функции Lisp создаёт новые \textit{привязки} (\textit{bindings}) для
хранения аргументов, переданных при вызове этой функции. Привязка является проявлением
переменной во время выполнения. Отдельная переменная~-- сущность, на которую вы можете
сослаться в исходном коде своей программы,~-- может иметь множество различных привязок за
время выполнения программы. Отдельная переменная даже может иметь множество привязок в
одно и то же время: параметры рекурсивной функции, например, связываются заново (rebound)
при каждом вызове этой функции.
Другой формой, позволяющей вводить новые переменные, является специальный оператор
\lstinline{LET}. Шаблон формы \lstinline{LET} имеет следующий вид:
\begin{myverb}
(let (variable*) body-form*)
\end{myverb}
\noindent{}где каждая \textit{variable} является формой инициализации переменной. Каждая
форма инициализации является либо списком, содержащим имя переменной и форму начального
значения, либо, как сокращение для инициализации переменной в значение \lstinline{NIL},
прос\-то именем переменной. Следующая форма \lstinline{LET}, например, связывает три
переменные \lstinline{x}, \lstinline{y} и~\lstinline{z} с начальными значениями
\lstinline{10}, \lstinline{20} и~\lstinline{NIL}:
\begin{myverb}
(let ((x 10) (y 20) z)
...)
\end{myverb}
При вычислении формы \lstinline{LET} сначала вычисляются все формы начальных значений. Затем,
перед выполнением форм тела, создаются и инициализируются в соответствующие начальные
значения новые привязки. Внутри тела \lstinline{LET} имена переменных ссылаются на только что
вновь созданные привязки. После \lstinline{LET} имена продолжают ссылаются на то, на что они
ссылались перед \lstinline{LET} (если они на что-то ссылались).
Значение последнего выражения тела возвращается как значение выражения \lstinline{LET}. Как и
параметры функций, переменные, вводимые \lstinline{LET}, связываются заново (rebound) каждый
раз, когда поток управления заходит в \lstinline{LET}\footnote{Переменные в формах \lstinline{LET} и
параметры функций создаются с помощью одного и того же механизма. На самом деле в
некоторых диалектах Lisp (но не в Common Lisp) \lstinline{LET} является просто макросом,
который раскрывается в вызов анонимной функции. Таким образом, в таких диалектах:
\begin{myverb}
(let ((x 10)) (format t "~a" x))
\end{myverb}
\noindent{}является формой макроса, которая раскрывается в:
\begin{myverb}
((lambda (x) (format t "~a" x)) 10)
\end{myverb}
}\hspace{\footnotenegspace}.
\textit{Область видимости} (\textit{scope}) параметров функций и переменных
\lstinline{LET}~-- область программы, где имя переменной может быть использовано для ссылки
на привязку переменной,~-- ограничивается формой, которая вводит переменную. Такая форма
(определение функции или \lstinline{LET}) называется \textit{связывающей формой}
(\textit{binding form}). Как вы скоро увидите, два типа переменных (лексические и
динамические) используют два несколько отличающихся механизма области видимости, но в
обоих случаях область видимости ограничена связывающей формой.
Если вы записываете вложенные связывающие формы, которые вводят переменные с одинаковыми
именами, то привязки внутренних переменных скрывают внешние привязки. Например, при вызове
следующей функции для параметра x создаётся привязка для хранения аргумента функции. Затем
первая \lstinline{LET} создаёт новую привязку с начальным значением~\lstinline{2}, а внутренняя
\lstinline{LET} создаёт ещё одну привязку с начальным значением~\lstinline{3}. Комментарии справа
указывают область видимости каждой привязки.
\begin{myverb}
(defun foo (x)
(format t "Параметр: ~a~%" x) ; |<------ x - аргумент
(let ((x 2)) ; |
(format t "Внешний LET: ~a~%" x) ; | |<---- x = 2
(let ((x 3)) ; | |
(format t "Внутренний LET: ~a~%" x)) ; | | |<-- x = 3
(format t "Внешний LET: ~a~%" x)) ; | |
(format t "Параметр: ~a~%" x)) ; |
\end{myverb}
Каждое обращение к \lstinline{x} будет ссылаться на привязку с наименьшей окружающей областью
видимости. Как только поток управления покидает область видимости какой-то связывающей
формы, привязка из непосредственно окружающей области видимости перестаёт скрываться,
и~\lstinline{x} ссылается уже на неё. Таким образом, результатом вызова \lstinline{foo} будет
следующий вывод:
\begin{myverb}
CL-USER> (foo 1)
Параметр: 1
Внешний LET: 2
Внутренний LET: 3
Внешний LET: 2
Параметр: 1
NIL
\end{myverb}
В~последующих главах я проанализирую другие конструкции, которые также служат связывающими
формами (вообще, любая конструкция, вводящая новые имена переменных, могущие
использоваться только внутри этой конструкции, является связывающей формой).
Например, в главе~\ref{ch:07} вы встретите цикл \lstinline{DOTIMES}, простой цикл-счётчик. Он
вводит переменную, которая содержит значение счётчика, увеличивающегося на каждой итерации
цикла. Например, следующий цикл, печатающий числа от~0 до~9, связывает переменную~\lstinline{x}:
\begin{myverb}
(dotimes (x 10) (format t "~d " x))
\end{myverb}
Ещё одной связывающей формой является вариант \lstinline{LET}: \lstinline{LET*}. Различие
состоит в том, что в \lstinline{LET} имена переменных могут быть использованы только в теле
\lstinline{LET} (части \lstinline{LET}, идущей после списка переменных), а в \lstinline{LET*}-формы
начальных значений для каждой переменной могут ссылаться на переменные, введённые ранее в
списке переменных. Таким образом, вы можете записать следующее:
\begin{myverb}
(let* ((x 10)
(y (+ x 10)))
(list x y))
\end{myverb}
\noindent{}но не так:
\begin{myverb}
(let ((x 10)
(y (+ x 10)))
(list x y))
\end{myverb}
Однако вы можете добиться такого же результата при помощи вложенных \lstinline{LET}.
\begin{myverb}
(let ((x 10))
(let ((y (+ x 10)))
(list x y)))
\end{myverb}
\section{Лексические переменные и замыкания}
По умолчанию все связывающие формы в Common Lisp вводят переменные \textit{лексической
области видимости} (\textit{lexically scoped}). На переменные лексической области
видимости можно ссылаться только в коде, который текстуально находится внутри связывающей
формы. Лексическая область видимости должна быть знакома каждому, кто программировал на
Java, C, Perl или Python, поскольку все они предоставляют <<локальные>> переменные, имеющие
лексическую область видимости. Программисты на Algol также должны чувствовать себя хорошо,
так как этот язык первым ввёл лексическую область видимости в 1960-х.
Однако лексические переменные Common Lisp несколько искажают понятие лексической
переменной, по крайней мере в сравнении с оригинальной моделью Algol. Это искажение
проявляется при комбинировании лексической области видимости со вложенными функциями. По
правилам лексической области видимости, только код, текстуально находящийся внутри
связывающей формы, может ссылаться на лексическую переменную. Но что произойдёт, когда
анонимная функция содержит ссылку на лексическую переменную из окружающей области
видимости? Например, в следующем выражении:
\begin{myverb}
(let ((count 0)) #'(lambda () (setf count (+ 1 count))))
\end{myverb}
\noindent{}ссылка на count внутри формы \lstinline{LAMBDA} допустима в соответствии с правилами
лексической области видимости. Однако анонимная функция, содержащая ссылку, будет
возвращена как значение формы \lstinline{LET}, и она может быть вызвана с помощью
\lstinline{FUNCALL} кодом, который \textit{не} находится в области видимости
\lstinline{LET}. Так что же произойдёт? Как выясняется, если count является лексической
переменной, все работает. Привязка \noindent{count}, созданная, когда поток управления зашёл в форму
\lstinline{LET}, остаётся столько, сколько это необходимо, в данном случае до тех пор, пока
что-то сохраняет ссылку на функциональный объект, возвращённый формой
\lstinline{LET}. Анонимная функция называется \textit{замыканием} (\textit{closure}), потому
что она <<замыкается вокруг>> привязки, созданной \lstinline{LET}.
Ключевым моментом для понимания замыканий является то, что захватывается не значение
переменной, а привязка. Поэтому замыкание может не только иметь доступ к значению
переменной, вокруг которой оно <<замкнуто>>, но и присваивать ей новые значения, которые
будут сохраняться между вызовами замыкания. Например, вы можете захватить замыкание,
созданное предыдущим выражением, в глобальную переменную следующим образом:
\begin{myverb}
(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))
\end{myverb}
Затем при каждом его вызове значение счётчика будет увеличиваться на единицу.
\begin{myverb}
CL-USER> (funcall *fn*)
1
CL-USER> (funcall *fn*)
2
CL-USER> (funcall *fn*)
3
\end{myverb}
Отдельное замыкание может <<замыкаться вокруг>> нескольких привязок переменных, просто
ссылаясь на них. Также множество замыканий могут захватывать одну и ту же
привязку. Например, следующее выражение возвращает список трёх замыканий, первое из
которых увеличивает значение привязки count, вокруг которой оно <<замкнуто>>, второе~--
уменьшает его, а третье~-- возвращает текущее значение:
\begin{myverb}
(let ((count 0))
(list
#'(lambda () (incf count))
#'(lambda () (decf count))
#'(lambda () count)))
\end{myverb}
\section{Динамические (специальные) переменные}
Привязки с лексической областью видимости помогают поддерживать код понятным, путём
ограничения области видимости, в которой, буквально говоря, данное имя имеет смысл. Вот
почему большинство современных языков программирования использует лексическую область
видимости для локальных переменных. Однако иногда вам действительно может понадобиться
глобальная переменная~-- переменная, к которой вы можете обратиться из любой части своей
программы. Хотя неразборчивое использование глобальных переменных может привести к
<<спагетти-коду>> так же быстро, как и неумеренное использование goto, глобальные переменные
имеют разумное использование и существуют в том или ином виде почти в каждом языке
программирования\pclfootnote{Java маскирует глобальные переменные как публичные статические
поля, C использует внешние (extern) переменные, а переменные уровня модулей в Python и
переменные уровня пакетов в Perl аналогично могут быть доступны отовсюду.}. И, как вы
сейчас увидите, глобальные переменные Lisp~-- динамические переменные~-- одновременно и
более удобны, и более гибки.
Common Lisp предоставляет два способа создания глобальных переменных: \lstinline{DEFVAR} и
\lstinline{DEFPARAMETER}. Обе формы принимают имя переменной, начальное значение и
опциональную строку документации. После создания переменной с помощью \lstinline{DEFVAR} или
\lstinline{DEFPARAMETER} имя может быть использовано где угодно для ссылки на текущую
привязку этой глобальной переменной. Как вы заметили в предыдущих главах, по соглашению
глобальные переменные именуются именами, начинающимися и заканчивающимися~\lstinline{*}. Далее в этой
главе вы увидите, почему очень важно следовать этому соглашению по именованию. Примеры
\lstinline{DEFVAR} и \lstinline{DEFPARAMETER} выглядят следующим образом:
\begin{myverb}
(defvar *count* 0
"Число уже созданных виджетов.")
(defparameter *gap-tolerance* 0.001
"Допустимое отклонение интервала между виджетами.")
\end{myverb}
Различие между этими двумя формами состоит в том, что \lstinline{DEFPARAMETER} всегда
присваивает начальное значение названной переменной, а \lstinline{DEFVAR} делает это, только
если переменная не определена. Форма \lstinline{DEFVAR} также может использоваться без
начального значения для определения глобальной переменной без установки её значения. Такая
переменная называется \textit{несвязанной} (\textit{unbound}).
На деле вам следует использовать \lstinline{DEFVAR} для определения переменных, которые
будут содержать данные, которые вы хотите сохранять даже при изменениях исходного кода,
использующего эту переменную. Например, представьте, что две переменные, определённые
ранее, являются частью приложения управления <<фабрикой
виджетов>>\translationnote{\textit{Фабрика}~-- стандартный шаблон
проектирования.}. Правильным будет определить переменную \lstinline{*count*} с помощью
\lstinline{DEFVAR}, так как число уже созданных виджетов не становится недействительным
лишь потому, что мы сделали некоторые изменения в коде создания виджетов\footnote{Если вам
специально нужно сбросить переменную, созданную с помощью \lstinline{DEFVAR}, вы можете
установить её напрямую с помощью \lstinline{SETF} или сделать её несвязанной с помощью
\lstinline{MAKUNBOUND} и перевычислить форму \lstinline{DEFVAR}.}\hspace{\footnotenegspace}.
С другой стороны, переменная \lstinline{*gap-tolerance*}, вероятно, влияет некоторым образом на
поведение самого кода создания виджетов. Если вы решите, что вам нужно меньшее или большее
допустимое отклонение, и, следовательно, измените значение в форме
\lstinline{DEFPARAMETER}, вы захотите, чтобы изменение вступило в силу при перекомпиляции
и перезагрузке файла.
После определения переменной с помощью \lstinline{DEFVAR} или \lstinline{DEFPARAMETER} вы
можете ссылаться на неё откуда угодно. Например, вы можете определить следующую функцию
для увеличения числа созданных виджетов:
\begin{myverb}
(defun increment-widget-count () (incf *count*))
\end{myverb}
Преимуществом глобальных переменных является то, что вам не нужно передавать их в
функции. Большинство языков программирования хранят потоки стандартного ввода и вывода в
глобальных переменных именно по этой причине: вы никогда не знаете, когда именно вы
захотите напечатать что-либо на стандартный вывод, и вы не хотите, чтобы каждая функция
принимала и передавала далее аргументы, содержащие эти потоки, только потому, что
нижележащему коду они могут понадобиться.
Однако, поскольку значение, такое как поток стандартного вывода, хранится в глобальной
переменной и вы написали код, ссылающийся на эту глобальную переменную, порой является
заманчивым попытаться временно изменить поведение этого кода путём изменения значения
переменной.
Например, представьте, что вы работаете над программой, содержащей некоторые
низкоуровневые функции журналирования, печатающие в поток, хранящийся в глобальной
переменной \lstinline{*standard-output*}. Теперь представьте, что в какой-то части программы вы
хотите перенаправить весь вывод, генерируемый этими функциями, в файл. Вы можете открыть
файл и присвоить полученный поток переменной \lstinline{*standard-output*}. После этого
вышеупомянутые функции будут слать свой вывод в этот файл.
Это работает замечательно, пока вы не забудете восстановить исходное значение
\lstinline{*standard-output*} после завершения действий. Если вы забудете восстановить
\lstinline{*standard-output*}, весь остальной код программы, использующий
\lstinline{*standard-output*}, также будет слать свой вывод в файл\footnote{Метод временного
переназначения \lstinline{*standard-output*} также плох, если система многонитевая
(multithreaded): если несколько нитей (threads) управления попытаются напечатать в
различные потоки (streams) вывода в одно и то же время, они все попытаются назначить
глобальной переменной поток, который они хотят использовать, чем помешают друг другу. Вы
можете использовать блокировку для контроля доступа к глобальной переменной, но тогда вы
не получите пользы от распараллеливания, так как какая-нибудь нить при печати блокирует
остальные нити до завершения этой операции, даже если эти нити хотят печатать в другой
поток.}\hspace{\footnotenegspace}.
Но похоже, что то, что вам действительно нужно,~-- это способ обернуть часть кода во что-то
говорящее: <<Весь нижележащий код (все функции, которые он вызывает, все функции, которые
вызывают эти функции, и так далее до функций самого низкого уровня) должен использовать
\textit{это} значение для глобальной переменной \lstinline{*standard-output*}>>. А затем, по завершении
работы функции верхнего уровня, старое значение \lstinline{*standard-output*} должно быть
автоматически восстановлено.
Оказывается, что это именно те возможности, что предоставляет вам другой вид переменных
Common Lisp: динамические переменные. Когда вы связываете динамическую переменную,
например с \lstinline{LET}-переменной или с параметром функции, привязка, создаваемая во
время входа в связывающую форму, заменяет глобальную привязку на все время выполнения
связывающей формы. В~отличие от лексических привязок, к которым можно обращаться только из
кода, находящегося в лексической области видимости связывающей формы, к динамическим
привязкам можно обращаться из любого кода, вызываемого во время выполнения связывающей
формы\pclfootnote{Технический термин, обозначающий интервал, в течение которого к привязке
можно обратиться,~-- \textit{протяжённость} (\textit{extent}) привязки. Таким образом,
область видимости и протяжённость являются взаимодополняющими понятиями: область
видимости относится к пространству, а протяжённость~-- ко времени. Лексические
переменные имеют лексическую область видимости и неопределённую протяжённость, имея
в виду то, что они существуют неопределённое время, определяемое тем, как долго они нужны
кому-либо. Динамические переменные, наоборот, имеют неопределённую область видимости,
так как они доступны отовсюду, но динамическую протяжённость. Для того чтобы запутать
вас ещё больше, стоит упомянуть, что комбинацию неопределённой области видимости и
динамической протяжённости часто неверно называют \textit{динамической областью
видимости}.}. И оказывается, что все глобальные переменные на самом деле являются
динамическими.
Таким образом, если вы хотите временно переопределить \lstinline{*standard-output*}, это можно
сделать, просто пересвязав её, например, с помощью \lstinline{LET}.
\begin{myverb}
(let ((*standard-output* *some-other-stream*))
(stuff))
\end{myverb}
В~любом коде, который выполняется в результате вызова \lstinline{stuff}, ссылки на \lstinline{*standard-output*}
будут использовать привязку, установленную с помощью \lstinline{LET}. А после того как \lstinline{stuff}
завершится и поток управления покинет \lstinline{LET}, новая привязка \lstinline{*standard-output*}
исчезнет, и последующие обращения к \lstinline{*standard-output*} будут видеть привязку, бывшую до
\lstinline{LET}. В~любой момент времени самая последняя установленная привязка скрывает все
остальные. Можно представить, что каждая новая привязка данной динамической переменной
помещается в стек привязок этой переменной, и ссылки на эту переменную всегда используют
последнюю установленную привязку. После выхода из связывающей формы созданные в ней
привязки убираются из стека, делая видимыми предыдующие привязки\pclfootnote{Хотя стандарт и
не указывает, как должна быть реализована многонитевость (multithreading) в Common Lisp,
реализации, которые предоставляют её, следуют практике, установленной на Lisp-машинах, и
создают динамические привязки для каждой нити. Обращение к глобальной переменной найдёт
привязку, либо установленную последней в текущей нити, либо глобальную привязку.}.
Простой пример показывает, как это работает:
\begin{myverb}
(defvar *x* 10)
(defun foo () (format t "X: ~d~%" *x*))
\end{myverb}
\lstinline{DEFVAR} создаёт глобальную привязку переменной \lstinline{*x*} со значением~\lstinline{10}. Обращение к
\lstinline{*x*} в \lstinline{foo} будет искать текущую привязку динамически. Если вы вызовете \lstinline{foo} на верхнем
уровне (top level), глобальная привязка, созданная \lstinline{DEFVAR}, будет
единственной доступной привязкой, поэтому будет напечатано~\lstinline{10}.
\begin{myverb}
CL-USER> (foo)
X: 10
NIL
\end{myverb}
Но вы можете использовать \lstinline{LET} для создания новой привязки, которая временно
скроет глобальную привязку, и \lstinline{foo} напечатает другое значение.
\begin{myverb}
CL-USER> (let ((*x* 20)) (foo))
X: 20
NIL
\end{myverb}
Теперь снова вызовем \lstinline{foo} без \lstinline{LET}, она опять будет видеть глобальную
привязку.
\begin{myverb}
CL-USER> (foo)
X: 10
NIL
\end{myverb}
Теперь определим новую функцию.
\begin{myverb}
(defun bar ()
(foo)
(let ((*x* 20)) (foo))
(foo))
\end{myverb}
Обратите внимание, что средний вызов \lstinline{foo} находится внутри \lstinline{LET}, которая связывает
\lstinline{*x*} с новым значением~\lstinline{20}. При вызове bar вы получите следующий результат:
\begin{myverb}
CL-USER> (bar)
X: 10
X: 20
X: 10
NIL
\end{myverb}
Как вы можете заметить, первый вызов \lstinline{foo} видит глобальную привязку со
значением~\lstinline{10}. Средний вызов видит новую привязку со значением~\lstinline{20}. А после
\lstinline{LET} \lstinline{foo} снова видит глобальную привязку.
Как и с лексической привязкой, присваивание нового значения влияет только на текущую
привязку. Чтобы увидеть это, вы можете переопределить \lstinline{foo}, добавив присваивание значения
переменной~\lstinline{*x*}.
\begin{myverb}
(defun foo ()
(format t "Перед присваиванием~18tX: ~d~%" *x*)
(setf *x* (+ 1 *x*))
(format t "После присваивания~18tX: ~d~%" *x*))
\end{myverb}
Теперь \lstinline{foo} печатает значение \lstinline{*x*}, увеличивает его на единицу, а затем
печатает его снова. Если вы просто запустите \lstinline{foo}, то увидите следующее:
\begin{myverb}
CL-USER> (foo)
Перед присваиванием X: 10
После присваивания X: 11
NIL
\end{myverb}
Ничего удивительного. Теперь запустим \lstinline{bar}.
\begin{myverb}
CL-USER> (bar)
Перед присваиванием X: 11
После присваивания X: 12
Перед присваиванием X: 20
После присваивания X: 21
Перед присваиванием X: 12
После присваивания X: 13
NIL
\end{myverb}
Обратите внимание, начальное значение \lstinline{*x*} равно~\lstinline{11}: предыдущий вызов
\lstinline{foo} действительно изменил глобальное значение. Первый вызов \lstinline{foo} из
\lstinline{bar} увеличивает глобальную привязку до~\lstinline{12}. Средний вызов не видит глобальную
привязку из-за \lstinline{LET}. А~затем последний вызов снова может видеть глобальную
привязку и увеличивает её с \lstinline{12} до~\lstinline{13}.
Так как это работает? Как \lstinline{LET} знает, когда связывает \lstinline{*x*}, что
подразумевается создание динамической привязки вместо обычной лексической? Она знает,
поскольку имя было объявлено \textit{специальным}\pclfootnote{Вот почему динамические
переменные также иногда называют \textit{специальными перменными} (\textit{special
variables}).}. Имя каждой переменной, определённой с по\-мощью \lstinline{DEFVAR} и
\lstinline{DEFPARAMETER}, автоматически глобально объявляется спе\-циаль\-ным. Это означает,
что когда бы вы не использовали это имя в связывающей форме (в форме \lstinline{LET}, или
как параметр функции, или в любой другой конструкции, которая создаёт новую привязку
переменной), вновь создаваемая привязка будет динамической. Вот почему \lstinline{*соглашение*}
\lstinline{*по*} \lstinline{*именованию*} так важно: будет не очень хорошо, если вы используете имя,
о котором вы думаете как о лексической переменной, а эта переменная окажется глобальной
специальной. С одной стороны, код, который вы вызываете, сможет изменить значение этой
связи; с другой,~-- вы сами можете скрыть связь, установленную кодом, находящимся выше по
стеку. Если вы всегда будете именовать глобальные переменные, используя соглашение по
именованию~\lstinline{*}, вы никогда случайно не воспользуетесь динамической связью, желая создать
лексическую.
Также возможно локально объявить имя специальным. Если в связывающей форме вы объявите имя
специальным, привязка, созданная для этой переменной, будет динамической, а не
лексической. Другой код может локально определить имя специальным, чтобы обращаться к
динамической привязке. Однако локальные специальные переменные используются относительно
редко, поэтому вам не стоит беспокоиться о них\footnote{Если вы хотите знать об этом, то
можете взглянуть на \lstinline{DECLARE}, \lstinline{SPECIAL} и \lstinline{LOCALLY} в HyperSpec.}\hspace{\footnotenegspace}.
Динамические привязки делают глобальные переменные гораздо более гибкими, но важно понимать,
что они позволяют осуществлять незаметные действия на расстоянии. Связывание глобальной
переменной имеет два дальнодействующих эффекта: оно может изменить поведение нижележащего кода,
а также открывает нижележащему коду возможность присваивания нового значения привязке,
установленной выше по стеку. Вы должны использовать динамические переменные только в том
случае, если вам нужно получить преимущества от одного или обоих из этих эффектов.
\section{Константы}
Еще одним видом переменных, вообще не упомянутых ранее, являются оксюморонические
<<константные переменные>>. Все константы являются глобальными и определяются с помощью
\lstinline{DEFCONSTANT}. Базовая форма \lstinline{DEFCONSTANT} подобна \lstinline{DEFPARAMETER}.
\begin{myverb}
(defconstant name initial-value-form [ documentation-string ])
\end{myverb}
Как и в случае с \lstinline{DEFPARAMETER}, \lstinline{DEFCONSTANT} оказывает глобальный
эффект на используемое имя: после этого имя может быть использовано только для обращения к
константе; оно не может быть использовано как параметр функции или быть пересвязано с
помощью любой другой связывающей формы. Поэтому многие программисты на Lisp следуют
соглашению по именованию и используют для констант имена, начинающиеся и заканчивающиеся
знаком~\lstinline{+}. Этому соглашению следуют немного в меньшей степени, чем соглашению для
глобальных динамических имён, но оно является хорошей идеей по сходным
причинам\footnote{Несколько ключевых констант, определённых самим языком, не следуют этому
соглашению: в первую очередь \lstinline{T} и~\lstinline{NIL}. Это иногда раздражает, например
когда кто-то хочет использовать~\lstinline{t} как имя глобальной переменной. Другим примером
является~\lstinline{PI}, которая содержит наилучшую аппроксимацию математической константы
$\pi$ в виде числа с плавающей точкой.}\hspace{\footnotenegspace}.
Ещё один важный момент: несмотря на то что язык позволяет вам переопределять константы
путём перевычисления \lstinline{DEFCONSTANT} с другой формой начального значения, не
определено то, что именно произойдёт после такого переопределения. На практике
большинство реализаций требует, чтобы вы перевычислили любой код, ссылающийся на
константу, чтобы изменение вступило в силу, так как старое значение могло быть встроено
(inlined). Следовательно, правильным будет использовать \lstinline{DEFCONSTANT} для
определения только тех вещей, которые \textit{действительно} являются константами, такие
как значение NIL. Для вещей, которые вам может когда-нибудь понадобится изменить, следует
использовать \lstinline{DEFPARAMETER}.
\section{Присваивание}
После создания привязки вы можете совершать с ней два действия: получить текущее значение
и установить ей новое значение. Как вы видели в главе~\ref{ch:04}, символ вычисляется в
значение переменной, которую он именует, поэтому вы можете получить текущее значение,
просто обратившись к переменной. Для присваивания нового значения привязке используйте
макрос \lstinline{SETF}, являющийся в Common Lisp оператором присваивания общего назначения.
Базовая форма \lstinline{SETF} следующая:
\begin{myverb}
(setf place value)
\end{myverb}
Так как \lstinline{SETF} является макросом, он может оценить форму <<места>>, которому он
осуществляет присваивание, и раскрыться в соответствующие низкоуровневые
операции, осуществляющие необходимые действия. Когда <<место>> является переменной, этот
макрос раскрывается в вызов специального оператора \lstinline{SETQ}, который, как специальный
оператор, имеет доступ и к лексическим, и к динамическим привязкам\footnote{Некоторые
программисты на Lisp старой школы предпочитают использовать \lstinline{SETQ} с переменными,
но современный стиль склоняется к использованию \lstinline{SETF} для всех операций
присваивания.}\hspace{\footnotenegspace}. Например, для присваивания значения~\lstinline{10} переменной~\lstinline{x} вы можете написать
это:
\begin{myverb}
(setf x 10)
\end{myverb}
Как я рассказал ранее, присваивание нового значения привязке не оказывает никакого влияния
на остальные привязки этой переменной. И оно не оказывает никакого влияния на значение,
которое хранилось в привязке до присваивания. Таким образом, \lstinline{SETF} в следующей
функции:
\begin{myverb}
(defun foo (x) (setf x 10))
\end{myverb}
\noindent{}не окажет никакого влияния на любое значение вне~\lstinline{foo}. Привязка, которая создаётся при
вызове~\lstinline{foo}, устанавливается в~\lstinline{10}, незамедлительно заменяя то значение, что было передано в
качестве аргумента. В~частности, следующая форма:
\begin{myverb}
(let ((y 20))
(foo y)
(print y))
\end{myverb}
\noindent{}напечатает \lstinline{20}, а не~\lstinline{10}, так как именно оно является значением~\lstinline{y}, которое передаётся
\lstinline{foo}, где уже является значением переменной~\lstinline{x} перед тем, как \lstinline{SETF}
даёт~\lstinline{x} новое значение.
\lstinline{SETF} также может осуществить последовательное присваивание множеству
<<мест>>. Например, вместо следующего:
\begin{myverb}
(setf x 1)
(setf y 2)
\end{myverb}
\noindent{}вы можете записать так:
\begin{myverb}
(setf x 1 y 2)
\end{myverb}
\lstinline{SETF} возвращает присвоенное значение, поэтому вы можете вкладывать вызовы
\lstinline{SETF}, как в следующем примере, который присваивает и~\lstinline{x}, и~\lstinline{y} одинаковое случайное
значение:
\begin{myverb}
(setf x (setf y (random 10)))
\end{myverb}
\section{Обобщённое присваивание}
Привязки переменных, конечно, не являются единственными <<местами>>, которые могут содержать
значения. Common Lisp поддерживает составные структуры данных, такие как массивы,
хэш-таблицы, списки, а также определённые пользователем структуры данных~-- такие
структуры состоят из множества <<мест>>, способных содержать значения.
Я опишу эти структуры данных в последующих главах, но так как мы рассматриваем
присваивание, вы должны знать, что \lstinline{SETF} может присвоить значение любому
<<месту>>. Когда я буду описывать различные составные структуры данных, я буду указывать,
какие функции могут использоваться как <<места, обрабатываемые \lstinline{SETF}>>
(<<\lstinline{SETF}able places>>). Кратко же можно сказать, что если вам нужно присвоить
значение <<месту>>, почти наверняка следует использовать \lstinline{SETF}. Возможно даже
расширить \lstinline{SETF}, для того чтобы он мог осуществлять присваивание определённым
пользователем <<местам>>, хотя я не описываю такиз возможностей\footnote{Взгляните на
\lstinline{DEFSETF}, \lstinline{DEFINE-SETF-EXPANDER} для получения дополнительной
информации.}\hspace{\footnotenegspace}.
В~этом отношении \lstinline{SETF} не отличается от оператора присваивания~\lstinline{=} языков,
произошедших от~C. В~этих языках оператор~\lstinline{=} присваивает новые значения переменным,
элементам массивов, полям классов. В~языках, таких как Perl и Python, которые поддерживают
хэш-таблицы как встроенные типы данных, \lstinline{=}~может также устанавливать значения
элементов хэш-таблицы. Таб.~\ref{table:06-1} резюмирует различные способы, которыми
используется~\lstinline{=} в этих языках.
\begin{table}[h]
\begin{tabular}{|m{42mm}|>{\centering}m{23mm}|>{\centering}m{30mm}|>{\centering}m{25mm}|}
\hline
Присваивание ... & Java, C, C++ & Perl & Python \\
\hline
... переменной & \lstinline!x = 10;! & \lstinline!$x = 10;! & \lstinline!x = 10! \\
... элементу массива & \lstinline!a[0] = 10;! & \lstinline!$a[0] = 10;! & \lstinline!a[0] = 10! \\
... элементу хэш-таблицы & -- & \lstinline!$hash{'key'} = 10;! & \lstinline!hash['key'] = 10! \\
... полю объекта & \lstinline!o.field = 10;! & \lstinline!$o->{'field'} = 10;! & \lstinline!o.field = 10! \\
\hline
\end{tabular}
\caption{Присваивание с помощью = в других языках программирования}
\label{table:06-1}
\end{table}
\lstinline{SETF} работает сходным образом: первый <<аргумент>> \lstinline{SETF} является
<<местом>> для хранения значения, а второй является самим значением. Как и с оператором~\lstinline{=}
в этих языках, вы используете одинаковую форму и для выражения <<места>>, и для
получения значения\footnote{Широкая распространённость Algol-подобного синтаксиса для
присваивания с <<местом>> слева от~\lstinline{=} и новым значением справа от него породило термины
\textit{lvalue}, сокращённо от <<left value>>, что означает нечто, чему можно
присваивать, и \textit{rvalue}, означающее нечто, предоставляющее значение. Хакеры
компилятора обычно говорят: <<\lstinline{SETF} рассматривает свой первый аргумент как
lvalue>>.}\hspace{\footnotenegspace}. Таким образом, эквиваленты вышеприведённых на рис.~\ref{table:06-1} присваиваний для
Lisp следующие (\lstinline{AREF}~-- функция доступа к массиву, \lstinline{GETHASH} осуществляет
операцию поиска в хэш-таблице, а \lstinline{field} может быть функцией, которая обращается к слоту под
именем \lstinline{field} определённого пользователем объекта):
\begin{myverb}
Простая переменная: (setf x 10)
Массив: (setf (aref a 0) 10)
Хэш-таблица: (setf (gethash 'key hash) 10)
Слот с именем 'field': (setf (field o) 10)
\end{myverb}
Обратите внимание, что присваиваение с помощью \lstinline{SETF} <<месту>>, которое является
частью большего объекта, имеет ту же семантику, что и присваивание переменной: <<место>>
модифицируется без оказания какого-либо влияния на объект, который хранился там до
этого. И вновь это подобно тому, как ведёт себя~\lstinline{=} в Java, Perl и
Python\footnote{Программисты на C могут думать о переменных и других <<местах>> как об
указателях на действительный объект; присваивание переменной просто изменяет
то, на какой объект она указывает, а присваивание значения части составного объекта
подобно косвенному обращению по указателю к действительному объекту. Программисты на C++
должны обратить внимание, что поведение оператора~\lstinline{=} в C++ при обращении с объектами (а
именно копирование каждого поля) абсолютно отличается.}\hspace{\footnotenegspace}.
\section{Другие способы изменения <<мест>>}
В~то время как все присваивания можно выразить с помощью \lstinline{SETF}, некоторые образцы,
включающие присваивания нового значения, основанного на текущем значении, являются
достаточно общими для того, чтобы получить свои собственные операторы. Например, вы можете
увеличить число с помощью \lstinline{SETF} следующим образом:
\begin{myverb}
(setf x (+ x 1))
\end{myverb}
\noindent{}или уменьшить его так:
\begin{myverb}
(setf x (- x 1))
\end{myverb}
Но это слегка утомительно, по сравнению со стилем C: \lstinline{++x} и \lstinline{--x}. Вместо этого
вы можете использовать макросы \lstinline{INCF} и \lstinline{DECF}, которые увеличивают и
уменьшают <<место>> на определённую величину, по умолчанию 1.
\begin{myverb}
(incf x) === (setf x (+ x 1))
(decf x) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
\end{myverb}
\lstinline{INCF} и \lstinline{DECF} являются примерами определённого вида макросов, называемых
\textit{модифицирующими макросами} (\textit{modify macros}). Модифицирующие макросы
являются макросами, построенными поверх \lstinline{SETF}, которые модифицируют <<места>> путём
присваивания нового значения, основанного на их текущем значении. Главным преимуществом
таких макросов является то, что они более краткие, чем аналогичные операции, записанные с
помощью \lstinline{SETF}. Вдобавок модифицирующие макросы определены таким образом, чтобы
быть безопасными при использовании с <<местами>>, когда выражение <<места>> должно быть
вычислено лишь единожды. Несколько надуманным примером является следующее выражение,
которое увеличивает значение произвольного элемента массива:
\begin{myverb}
(incf (aref *array* (random (length *array*))))
\end{myverb}
Наивный перевод этого примера в выражение, использующее \lstinline{SETF}, может выглядеть
следующим образом:
\begin{myverb}
(setf (aref *array* (random (length *array*)))
(1+ (aref *array* (random (length *array*)))))
\end{myverb}
Однако это не работает, так как два последовательных вызова \lstinline{RANDOM} не
обязательно вернут одинаковое значение: это выражение, вероятно, получит значение одного
элемента массива, увеличит его, а затем сохранит его как новое значение другого элемента
массива. Однако выражение \lstinline{INCF} сделает все правильно, так как знает, как
правильно разобрать это выражение:
\begin{myverb}
(aref *array* (random (length *array*)))
\end{myverb}
\noindent{}чтобы извлечь те части, которые, возможно, могут иметь побочные эффекты, и гарантировать,
что они будут вычисляться лишь один раз. В~этом случае выражение \lstinline{INCF}, вероятно,
раскроется в нечто, более или менее подобное этому:
\begin{myverb}
(let ((tmp (random (length *array*))))
(setf (aref *array* tmp) (1+ (aref *array* tmp))))
\end{myverb}
Вообще, модифицирующие макросы гарантируют однократное вычисление слева направо своих
аргументов, а также подформ формы места (place form).
Макрос \lstinline{PUSH}, который вы использовали в примере с базой данных для добавления
элементов в переменную \lstinline{*db*}, является ещё одним модифицирующим макросом. Более подробно о
его работе и работе \lstinline{POP} и \lstinline{PUSHNEW} будет сказано в главе~\ref{ch:12}, где
я буду говорить о том, как представляются списки в Lisp.
И наконец, два слегка эзотерических, но полезных модифицирующих макроса~--
\lstinline{ROTATEF} и \lstinline{SHIFTF}. \lstinline{ROTATEF} циклически сдвигает значение между
<<местами>>. Например, если вы имеете две переменные, \lstinline{a}~и~\lstinline{b}, этот вызов:
\begin{myverb}
(rotatef a b)
\end{myverb}
\noindent{}обменяет значения двух переменных и вернёт \lstinline{NIL}. Так
как~\lstinline{a} и~\lstinline{b} являются переменными и вам не нужно беспокоиться о
побочных эффектах, предыдущее выражение \lstinline{ROTATEF} эквивалентно следующему:
\begin{myverb}
(let ((tmp a)) (setf a b b tmp) nil)
\end{myverb}
С другими видами <<мест>> эквивалентное выражение с использованием \lstinline{SETF} может быть
более сложным.
\lstinline{SHIFTF} подобен \lstinline{ROTATEF}, за исключением того, что вместо циклического
сдвига значений он просто сдвигает их влево: последний аргумент предоставляет значение,
которое перемещается в предпоследний аргумент, и так далее. Исходное значение первого
аргумента просто возвращается. Таким образом, следующее:
\begin{myverb}
(shiftf a b 10)
\end{myverb}
\noindent{}эквивалентно (и снова, так как вам не нужно беспокоиться о побочных эффектах) следующему:
\begin{myverb}
(let ((tmp a)) (setf a b b 10) tmp)
\end{myverb}
И \lstinline{ROTATEF}, и \lstinline{SHIFTF} могут использоваться с любым числом аргументов и,
как все модифицирующие макросы, гарантируют однократное их вычисление слева направо.
С базовыми знаниями функций и переменных Common Lisp в своём арсенале вы готовы перейти к
следующей особенности, которая ещё больше отличает Lisp от других языков программирования:
макросы.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End:
Something went wrong with that request. Please try again.