7.5 Установка новых связываний переменных

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

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

[Специальный оператор] let ({var | (var [value])}*) {declaration}* {form}*

Форма let может быть использована для связи множества переменных со значениями соответствующего множества форм.

Если быть точнее, форма

(let ((var1 value1)
      (var2 value2)
      ...
      (varm valuem))
  declaration1
  declaration2
  ...
  declarationp
  body1
  body2
  ...
  bodyn)

сначала последовательно выполняет выражения value1, value2 и т.д., сохраняя результаты. Затем все переменные varj параллельно привязываются к сохранённым значениям. Каждое связывание будет является лексическим, кроме тех, для которых указана декларация special. Затем последовательно выполняются выражения bodyk. Все из значения, кроме последнего, игнорируются (другими словами, тело let является неявным progn). Форма let возвращает значение bodyn (если тело пустое, что в принципе бесполезно, то let возвращает nil). Связывания переменных имеют лексическую область видимости и неограниченную продолжительность.

Вместо списка (varj valuej), можно записать просто varj. В таком случае varj инициализируется значением nil. В целях хорошего стиля рекомендуется, записывать varj только, если в неё будет что-нибудь записано (с помощью setq например), перед первым использованием. Если важно, чтобы первоначальное значение было nil, вместо некоторого неопределённого значения, тогда будет лучше записать (varj nil) или (varj ’()), если значение должно обозначать пустой список. Обратите внимание, что код

(let (x)
  (declare (integer x))
  (setq x (gcd y z))
  ...)

неправильный. Так как x объявлен без первоначального значения и также объявлено, что x это целое число, то произойдёт исключение, так как x при связывании получает nil значение, которое не принадлежит целочисленному типу.

Декларации могут использоваться в начале тела let. Смотрите declare.

Смотрите также destructuring-bind.


[Специальный оператор] let* ({var | (var [value])}*) {declaration}* {form}*

let* похожа на let, но связывания переменных осуществляются последовательно, а не параллельно. Это позволяет выражениям для значений переменных ссылаться на ранее связанные переменные.

Если точнее, форма

(let* ((var1 value1)
       (var2 value2)
       ...
       (varm valuem))
  declaration1
  declaration2
  ...
  declarationp
  body1
  body2
  ...
  bodyn)

сначала вычисляет выражение value1, затем с этим значением связывает переменную var1, затем вычисляет value2 и связывает с результатом переменную var2, и так далее. Затем последовательно вычисляются выражения bodyj. Значения всех выражений, кроме последнего, игнорируются. То есть тело формы let* является неявным progn. Форма let* возвращает результаты вычисления bodyn (если тело пустое, что, в принципе, бесполезно, let* возвращает nil). Связывания переменных имеют лексическую область видимости и неограниченную продолжительность.

Вместо списка (varj valuej), можно записать просто varj. В таком случае varj будет инициализирована в nil. В целях стиля, рекомендуется записывать varj, только ей будет что-нибудь присвоено с помощью setq перед первым использованием. Если необходимо инициализировать переменную значением nil, а не неопределённым, лучше писать (varj nil) для инициализации «ложью» или (varj ’()) для инициализации пустым списком.

В начале тела let* могут использоваться декларации. Смотрите declare.


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

progv является оператором, который позволяет создавать связывания одной и более динамических переменных, чьи имена устанавливаются во время выполнения. Последовательность форм (неявный progn) выполняется с динамическими переменными, что имена в списке symbols связаны с соответствующими значениями в списке values. (Если значений меньше, чем переменных, то соответствующие переменные получают соответствующие значения, а оставшиеся остаются без значений. Смотрите makunbound. Если значений больше, чем переменных, они игнорируются.) Результатом progv является результат последней формы. Связывания динамических переменных упраздняются при выходе из формы progv. Списки переменных и значений это вычисляемые значения. Это то, что отличает progv от, например, let, в которой имена переменных указываются явно в тексте программы.

progv полезна, в частности, для написания интерпретаторов языков встраиваемых в Lisp. Она предоставляет управление механизмом связывания динамических переменных.


[Макрос] flet ({(name lambda-list[[{declaration}* | doc-string]] {form}*)}*){declaration}* {form}*

[Макрос] labels ({(name lambda-list[[{declaration}* | doc-string]] {form}*)}*){declaration}* {form}*

[Макрос] macrolet ({(name varlist[[{declaration}* | doc-string]] {form}*)}*){declaration}* {form}*

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

Может быть определено любое количество функций. Каждое определение осуществляется формате, как в форме defun: сначала имя, затем список параметров (который может содержать &optional, &rest или &key параметры), затем необязательные декларации и строка документации, и, наконец, тело.

(flet ((safesqrt (x) (sqrt (abs x))))
  ;; Функция safesqrt используется в двух местах.
  (safesqrt (apply #’+ (map ’list #’safesqrt longlist))))

Конструкция labels идентична по форме конструкции flet. Эти конструкции различаются в том, что область видимости определённых функций для flet заключена только в теле, тогда как видимость в labels охватывает даже определения этих функций. Это значит, что labels может быть использована для определения взаимно рекурсивных функций, а flet не может. Это различие бывает полезно. Использование flet может локально переопределить глобальную функцию, и новое определение может ссылаться на глобальное. Однако такая же конструкция labels не будет обладать этим свойством.

(defun integer-power (n k)       ; Быстрое возведение
  (declare (integer n))          ; целого числа в степень
  (declare (type (integer 0 *) k))
  (labels ((expt0 (x k a)
             (declare (integer x a) (type (integer 0 *) k))
             (cond ((zerop k) a)
                   ((evenp k) (expt1 (* x x) (floor k 2) a))
                   (t (expt0 (* x x) (floor k 2) (* x a)))))
           (expt1 (x k a)
             (declare (integer x a) (type (integer 1 *) k))
             (cond ((evenp k) (expt1 (* x x) (floor k 2) a))
                   (t (expt0 (* x x) (floor k 2) (* x a))))))
    (expt0 n k 1)))

macrolet похожа на форму flet, но определяет локальные макросы, используя тот же формат записи, что и defmacro. Имена для макросов, установленные с помощью macrolet, имеют лексическую область видимости.

I have observed that, while most Common Lisp users pronounce macrolet to rhyme with “silhouette,” a small but vocal minority pronounce it to rhyme with “Chevrolet.” A very few extremists furthermore adjust their pronunciation of flet similarly: they say “flay.” Hey, hey! Tr`es outr´e.

Макросы часто должны быть раскрыты во «время компиляции» (общими словами, во время перед тем, как сама программа будет выполнена), таким образом, значения переменных во время выполнения не доступны для макросов, определённых с помощью macrolet.

X3J13 voted in March 1989 to retract the previous sentence and specify that the macro-expansion functions created by macrolet are defined in the lexical environment in which the macrolet form appears, not in the null lexical environment. Declarations, macrolet definitions, and symbol-macrolet definitions affect code within the expansion functions in a macrolet, but the consequences are undefined if such code attempts to refer to any local variable or function bindings that are visible in that lexical environment.

Однако, сущности, имеющие лексическую область видимости, видны внутри тела формы macrolet и видны в коде, который является результатом раскрытия макровызова. Следующий пример должен помочь в понимании:

;;; Пример macrolet.

(defun foo (x flag)
  (macrolet ((fudge (z)
                ;;Параметры x и flag в данной точке
                ;; недоступны; ссылка на flag была бы
                ;; одноимённую глобальную переменную.
                ‘(if flag
                     (* ,z ,z)
                     ,z)))
    ;;Параметры x и flag доступны здесь.
    (+ x
       (fudge x)
       (fudge (+ x 1)))))

Тело данного примера после разворачивания макросов превращается в

(+ x
   (if flag
       (* x x)
       x))
   (if flag
       (* (+ x 1) (+ x 1))
       (+ x 1)))

x и flag легитимно ссылаются на параметры функции foo, потому что эти параметры видимы в месте макровызова.

X3J13 voted in March 1988 to specify that the body of each function or expander function defined by flet, labels, or macrolet is implicitly enclosed in a block construct whose name is the same as the name of the function. Therefore return-from may be used to exit from the function.

X3J13 voted in March 1989 to extend flet and labels to accept any function-name (a symbol or a list whose car is setf—see section 7.1) as a name for a function to be locally defined. In this way one can create local definitions for setf expansion functions. (X3J13 explicitly declined to extend macrolet in the same manner.)

X3J13 voted in March 1988 to change flet, labels, and macrolet to allow declarations to appear before the body. The new descriptions are therefore as follows:


[Специальный оператор] symbol-macrolet ({(var expansion)}*){declaration}* {form}*

X3J13 проголосовал в июне 1988 адаптировать Common Lisp’овую систему объектов (CLOS). Часть этого является общий механизм, symbol-macrolet, для обработки заданных имён переменным, как если бы они были макровызовами без параметров. Эта функциональность полезно независимо от CLOS.

Формы forms выполняются как неявный progn в лексическом окружении, в котором любая ссылка на обозначенную переменную var будет заменена на соответствующее выражение expansion. Это происходит, как будто ссылка на переменную var является макровызовом без параметров. Выражение expansion вычисляется или обрабатывается в месте появления ссылки. Однако, следует отметить, что имена таких макросимволов работает в пространстве имен переменных, не в пространстве функций. Использование symbol-macrolet может быть в свою очередь перекрыто с помощью let или другой конструкцией, связывающей переменные. Например:

(symbol-macrolet ((pollyanna ’goody))
  (list pollyanna (let ((pollyanna ’two-shoes)) pollyanna)))
  (goody two-shoes), not (goody goody)

Выражение expansion для каждой переменной var вычисляется не во время связывания, а во время подстановки вместо ссылок на var. Конструкция возвращает значения последней вычисленной формы, или nil, если таких значений не было.

Смотрите документация macroexpand и macroexpand-1. Они раскрывают макросы символов, также как и обычные макросы.

Указанные декларации declarations перед телом обрабатываются так как описано в разделе 9.1.


[Макрос] define-symbol-macro symbol {form}

Символ symbol выступает в качестве макровызова. Одинарная форма содержит тело раскрытия. Символы не может являться определённой специальной переменной.