7.1 Константы и переменные

Так как некоторые Lisp’овые объекты данных используются для отображения программ, можно всегда обозначить константный объект данных с помощью записи без приукрашательств формы данного объекта. Однако порождается двусмысленность: константный это объект или фрагмент кода. Эту двусмысленность разрешает оператор quote.

В Common Lisp’е присутствуют два вида переменных, а именно: обычные переменные и имена функций. Между этими типами есть несколько сходств, и в некоторых случаях для взаимодействия с ними используются похожие функции, например boundp и fboundp. Однако для в большинстве случаев два вида переменных используются для совсем разных целей: один указывает на функции, макросы и операторы, а другие на объекты данных.

X3J13 voted in March 1989 to introduce the concept of a function-name, which may be either a symbol or a two-element list whose first element is the symbol setf and whose second element is a symbol. The primary purpose of this is to allow setf expander functions to be CLOS generic functions with user-defined methods. Many places in Common Lisp that used to require a symbol for a function name are changed to allow 2-lists as well; for example, defun is changed so that one may write (defun (setf foo) ...), and the function special operator is changed to accept any function-name. See also fdefinition.

By convention, any function named (setf f ) should return its first argument as its only value, in order to preserve the specification that setf returns its newvalue. See setf.

Implementations are free to extend the syntax of function-names to include lists beginning with additional symbols other than setf or lambda.

7.1.1 Ссылки на переменные

Значение обычной переменной может быть получено просто с помощью записи его имени как формы, которая будет выполнена. Будет ли данное имя распознано как имя специальной или лексической переменной зависит от наличия или отсутствия соответствующей декларации special. Смотрите главу 9.

Следующие функции и операторы позволяют ссылаться на значения констант и переменных.

[Специальный оператор] quote object

(quote x) возвращает x. object не выполняется и может быть любым объектом Lisp’а. Конструкция позволяет записать в программе любой объект, как константное значение. Например:

(setq a 43)
(list a (cons a 3))  (43 (43 . 3))
(list (quote a) (quote (cons a 3))  (a (cons a 3))

Так как quote форма так полезна, но записывать её трудоёмко, для неё определена стандартная аббревиатура: любая форма f с предшествующей одинарной кавычкой ( ’ ) оборачивается формой (quote  ) для создания (quote f ). Например:

(setq x ’(the magic quote hack))

обычно интерпретируется функцией read, как

(setq x (quote (the magic quote hack)))

Смотрите раздел 22.1.3.

It is an error to destructively modify any object that appears as a constant in executable code, whether within a quote special operator or as a self-evaluating form.

See section 24.1 for a discussion of how quoted constants are treated by the compiler.

Деструктивная модификация любого объекта, который представлен как константа в выполняемом коде с помощью специального оператора quote или как самовычисляемая форма, является ошибкой.

Смотрите раздел 24.1 для обсуждения того, как заквоченные константы обрабатываются компилятором.

X3J13 voted in March 1989 to clarify that eval and compile are not permitted either to copy or to coalesce (“collapse”) constants (see eq) appearing in the code they process; the resulting program behavior must refer to objects that are eql to the corresponding objects in the source code. Moreover, the constraints introduced by the votes on issues and on what kinds of objects may appear as constants apply only to compile-file (see section 24.1).


[Специальный оператор] function fn

Значением function всегда является функциональной интерпретацией fn. fn интерпретируется как, если бы она была использована на позиции функции в форме вызова функции. В частности, если fn является символом, возвращается определение функции, связанное с этим символом, смотрите symbol-function. Если fn является лямбда-выражением, тогда возвращается «лексическое замыкание», это значит функция, которая при вызове выполняет тело лямбда-выражения таким образом, чтобы правила лексического контекста выполнялись правильно.

X3J13 voted in June 1988 to specify that the result of a function special operator is always of type function. This implies that a form (function fn) may be interpreted as (the (function fn)).

It is an error to use the function special operator on a symbol that does not denote a function in the lexical or global environment in which the special operator appears. Specifically, it is an error to use the function special operator on a symbol that denotes a macro or special operator. Some implementations may choose not to signal this error for performance reasons, but implementations are forbidden to extend the semantics of function in this respect; that is, an implementation is not allowed to define the failure to signal an error to be a “useful” behavior.

Функция function принимает любое имя функции (символ или список, car элементы которого является setf—смотрите раздел 7.1) Также принимает принимает лямбда-выражение. Так можно записать (function (setf cadr)) для ссылки на функцию раскрытия setf для cadr.

Например:

(defun adder (x) (function (lambda (y) (+ x y))))

Результат (adder 3) является функцией, которая добавляет 3 к её аргументу:

(setq add3 (adder 3))
(funcall add3 5)  8

Это работает, потому что function создаёт замыкание над внутренним лямбда-выражением, которое может ссылаться на значение 3 переменной x даже после того, как выполнение вышло из функции adder.

Если посмотреть глубже, то лексическое замыкание обладает возможностью ссылаться на лексически видимые связывание, а не просто на значения. Рассмотрим такой код:

(defun two-funs (x)
  (list (function (lambda () x))
        (function (lambda (y) (setq x y)))))
(setq funs (two-funs 6))
(funcall (car funs))  6
(funcall (cadr funs) 43)  43
(funcall (car funs))  43

Функция two-funs возвращает список двух функций, каждая из которых ссылается на связывание переменной x, созданной в момент входа в функцию two-funs, когда она была вызвана с аргументом 6. Это связывание сначала имеет значение 6, но setq может изменить связывание. Лексическое замыкание для первого лямбда-выражения не является «создаёт снимок» значения 6 для x при создании замыкания. Вторая функция может использоваться для изменения связывания (на 43 например), и это изменённое значение станет доступным в первой функции.

В ситуации, когда замыкание лямбда-выражения над одним и тем же множеством связываний может создаваться несколько раз, эти полученные разные замыкания могут быть равны или не равны eq в зависимости от реализации. Например:

(let ((x 5) (funs ’()))
  (dotimes (j 10)
    (push #’(lambda (z)
              (if (null z) (setq x 0) (+ x z)))
          funs))
  funs)

Результат данного выражения является списком десяти замыканий. Каждое логически требует только связывания x. В любом случае это одно и то же связывание, но десять замыканий могут быть равны или не равны eq друг другу. С другой стороны, результат выражения

(let ((funs ’()))
  (dotimes (j 10)
    (let ((x 5))
      (push (function (lambda (z)
                        (if (null z) (setq x 0) (+ x z))))
            funs)))
  funs)

также является списком из десяти замыканий. Однако в этом случае, но одна из пар замыканий не будет равна eq, потому что каждое замыкание имеет своё связывание x отличное от другого. Связывания отличаются, так как в замыкании используется setq.

Вопрос различного поведения важен, поэтому рассмотрим следующее простое выражение:

(let ((funs ’()))
  (dotimes (j 10)
    (let ((x 5))
      (push (function (lambda (z) (+ x z)))
            funs)))
  funs)

Результатом является десять замыканий, которые могут быть равны eq попарно. Однако, можно подумать что связывания x для каждого замыкания разные, так как создаются в цикле, но связывания не могут различаться, потому что их значения идентичны и неизменяемы (иммутабельны), в замыканиях отсутствует setq для x. Компилятор может в таких случаях оптимизировать выражение так:

(let ((funs ’()))
  (dotimes (j 10)
    (push (function (lambda (z) (+ 5 z)))
          funs))
  funs)

после чего, в конце концов, замыкания точно могут быть равны. Общее правило такое, что реализация может в двух различных случаях выполнения формы function вернуть идентичные (eq) замыкания, если она может доказать, что два концептуально различающихся замыкания по факту ведут себя одинаково при одинаковых параметрах вызова. Это просто разрешается для оптимизации. Полностью корректная реализация может каждый раз при выполнении формы function возвращать новое замыкание не равное eq другим.

Часто компилятор может сделать вывод, что замыкание по факту не нуждается в замыкании над какими-либо связываниями переменных. Например, в фрагменте кода

(mapcar (function (lambda (x) (+ x 2))) y)

функция (lambda (x) (+ x 2)) не содержит ссылок на какие-либо внешние сущности. В этом важном случае, одно и то же «замыкание» может быть использовано в качестве результата всех выполнений оператора function. Несомненно, данное значение может и не быть объектом замыкания. Оно может быть просто скомпилированной функцией, не содержащей информации об окружении. Данный пример просто является частным случаем предыдущего разговора и включён в качестве подсказки для разработчиков знакомых с предыдущими методами реализации Lisp’а. Различие между замыканиями и другими видами функций слегка размыто, Common Lisp не определяет отображения для замыканий и метода различия замыканий и простых функций. Все что имеет значение, это соблюдение правил лексической области видимости.

Так как форма function используются часто, но её запись длинная, для неё определена стандартная аббревиатура: любая форма f с предшествующими #’ разворачивается в форму (function f ). Например,

(remove-if #’numberp ’(1 a b 3))

обычно интерпретируется функцией read как

(remove-if (function numberp) ’(1 a b 3))

Смотрите раздел 22.1.4.


[Функция] symbol-value symbol

symbol-value возвращает текущее значение динамической (специальной) переменной с именем symbol. Если символ не имеет значения, возникает ошибка. Смотрите boundp и makunbound. Следует отметить, что константные символы являются переменными, которые не могут быть изменены, таким образом symbol-value может использоваться для получения значения именованной константы. symbol-value от ключевого символа будет возвращать этот ключевой символ.

symbol-value не может получить доступ к значению лексической переменной.

В частности, эта функция полезна для реализации интерпретаторов для встраиваемых языков в Lisp’е. Соответствующая функция присваивания set. Кроме того, можно пользоваться конструкцией setf с symbol-value.


[Функция] symbol-function symbol

symbol-function возвращает текущее глобальное определение функции с именем symbol. В случае если символ не имеет определения функции сигнализируется ошибка, смотрите fboundp. Следует отметить что определение может быть функцией или объектом отображающим оператор или макрос. Однако, в последнем случае, попытка вызова объекта как функции будет является ошибкой. Лучше всего заранее проверить символ с помощью macro-function и special-operator-p и только затем вызвать функциональное значение, если оба предыдущих теста вернули ложь.

Эта функция полезна, в частности, для реализации интерпретаторов языков встроенных в Lisp.

symbol-function не может получить доступ к значению имени лексической функции, созданной с помощью flet или labels. Она может получать только глобальное функциональное значение.

Глобальное определение функции для некоторого символа может быть изменено с помощью setf и symbol-function. При использовании этой операции символ будет иметь только заданное определение в качестве своего глобального функционального значения. Любое предыдущее определение, было ли оно макросом или функцией, будет потеряно. Попытка переопределения оператора (смотрите таблицу 5.1) будет является ошибкой.

X3J13 voted in June 1988 to clarify the behavior of symbol-function in the light of the redefinition of the type function.


[Функция] fdefinition function-name

Функция похожа на symbol-function за исключением того, что её аргументы может быть любым именем функции (символ или списка, у которого car элемент равен setf—смотрите раздел 7.1). Функция возвращает текущее глобальное значение определения функции с именем function-name. Можно использовать fdefinition вместе с setf для изменения глобального определения функции связанной с переданным в параметре именем.


[Функция] boundp symbol

boundp является истиной, если динамическая (специальная) переменная с именем symbol имеет значение, иначе возвращает nil.

Смотрите также set и makunbound.


[Функция] fboundp symbol

fboundp является истиной, если символ имеет глобальное определение функции. Следует отметить, что fboundp является истиной, если символ указывает на оператор или макрос. macro-function и special-operator-p могут использоваться для проверки таких случаев.

Смотрите также symbol-function и fmakunbound.

Функция fboundp принимает любое имя функции (символ или список, car элементы которого является setf—смотрите раздел 7.1) Так можно записать (fboundp ’(setf cadr)) для определения существует ли функция setf для cadr.


[Функция] special-operator-p symbol

Функция special-operator-p принимает символ. Если символ указывает на оператор, тогда возвращается значение не-nil, иначе возвращается nil. Возвращённое не-nil значение является функцией, которая может быть использована для интерпретации (вычисления) специальной формы. FIXME

Возможно также то, что обе функции special-operator-p и macro-function будут истинными для одного и того же символа. Это потому, что реализация может содержать любой макрос как оператор для увеличения скорости. С другой стороны, определение макроса должно быть доступно для использования программами, которые понимают только стандартные операторы, перечисленные в таблице 5.1. FIXME


7.1.2 Присваивание

Следующая функциональность позволяет изменять значение переменной (если быть точнее, значению соединённому с текущим связыванием переменной). Такое изменение отличается от создания нового связывания. Конструкции для создания новых связываний переменных описаны в разделе 7.5.

[Специальный оператор] setq {var form}*

Оператор (setq var1 form1 var2 form2 ...) является «конструкцией присваивания простых переменных» Lisp’а. Вычисляется первая форма form1 и результат сохраняется в переменной var1, затем вычисляется form2 и результат сохраняется в переменной var2, и так далее. Переменные, конечно же, представлены символами, и интерпретируются как ссылки к динамическим или статическим переменным в соответствии с обычными правилами. Таким образов setq может быть использована для присваивания как лексических, так и специальных переменных.

setq возвращает последнее присваиваемое значение, другими словами, результат вычисления последнего аргумента. В другом случае, форма (setq) является корректной и возвращает nil. В форме должно быть чётное количество форм аргументов. Например, в

(setq x (+ 3 2 1) y (cons x nil))

x устанавливается в 6, y в (6), и setq возвращает (6). Следует отметить, что первое присваивание выполняется перед тем, как будет выполнено второе, тем самым каждое следующее присваивание может использовать значение предыдущих.

Смотрите также описание setf, «общая конструкция присваивания» Common Lisp’а, которая позволяет присваивать значения переменным, элементам массива, и другим местам.

Некоторые программисты выбирают путь отречения от setq, и всегда используют setf. Другие используют setq для простых переменных и setf для всех остальных.

Если любая var ссылается не на обычную переменную, а на связывание сделанное с помощью symbol-macrolet, тогда var обрабатывается как если psetf использовалось вместо setq.


[Макрос] psetq {var form}*

Форма psetq похожа на форму setq за исключением того, что выполняет присваивание параллельно. Сначала выполняются все формы, а затем переменные получают значения этих форм. Значение формы psetq nil. Например:

(setq a 1)
(setq b 2)
(psetq a b b a)
a  2
b  1

В этом примере, значения a и b меняются местами с помощью параллельного присваивания. (Если несколько переменных должны быть присвоены параллельно в рамках цикла, целесообразнее использовать конструкцию do.)

Смотрите также описание psetf, «общая конструкция параллельного присваивания» Common Lisp’а, которая позволяет присваивать переменным, элементам массива, и другим местам.

Если любая var ссылается не на обычную переменную, а на связывание сделанное с помощью symbol-macrolet, тогда var обрабатывается как если psetf использовалось вместо psetq.


[Функция] set symbol value

set позволяет изменить значение динамической (специальной) переменной. set устанавливает динамической переменной с именем symbol значение value.

Изменено будет только значение текущего динамического связывания. Если такого связывания нет, будет изменено наиболее глобальное значение. Например,

(set (if (eq a b) ’c ’d) ’foo)

установит значение с в foo или do* в foo, в зависимости от результата проверки (eq a b).

set в качестве результата возвращает значение value.

set не может изменить значение локальной (лексически связанной) переменной. Обычно для изменения переменных (лексических или динамических) используется оператор setq. set полезна в частности для реализации интерпретаторов языков встроенных в Lisp. Смотрите также progv, конструкция, которая создаёт связывания, а не присваивания динамических переменных.


[Функция] makunbound symbol
[Функция] fmakunbound symbol

makunbound упраздняет связывание динамической (специальной) переменной заданной символом symbol (упраздняет значение). fmakunbound аналогично упраздняет связь символа с глобальным определением функции. Например:

(setq a 1)
a  1
(makunbound ’a)
a  ошибка

(defun foo (x) (+ x 1))
(foo 4)  5
(fmakunbound ’foo)
(foo 4)  ошибка

Обе функции возвращают символ symbol в качестве результата.

Функция fmakunbound принимает любое имя функции (символ или список, car элементы которого является setf—смотрите раздел 7.1) Так можно записать (fmakunbound ’(setf cadr)) для удаления функции setf для cadr.