5.3 Формы верхнего уровня

Стандартный путь взаимодействия с реализацией Common Lisp через цикл чтение-выполнение-печать (read-eval-print loop): система циклично считывает форму из некоторого источника ввода (клавиатура, файл), выполняет её, затем выводит результат(ы) в некоторое устройство вывода (дисплей, другой файл). Допускается любая форма (выполняемый объект данных), однако существуют некоторые ормы разработанные для удобного применения в качестве форм верхнего уровня. Эти операторы верхнего уровня могут использоваться для определения глобальных функции (globally named functions), макросов, создания деклараций и определения глобальных значений для специальных переменных.

While defining forms normally appear at top level, it is meaningful to place them in non-top-level contexts. All defining forms that create functional objects from code appearing as argument forms must ensure that such argument forms refer to the enclosing lexical environment. Compilers must handle defining forms properly in all situations, not just top-level contexts. However, certain compile-time side effects of these defining forms are performed only when the defining forms occur at top level (see section 24.1).

Макросы обычно определяются с помощью оператора defmacro. Этот механизм достаточно сложен. Для подробностей смотрите главу 8.

5.3.1 Определение функций

Оператор defun служит для определения функций.

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

Выполнение формы defun приводит к тому, что символ name становиться глобальным именем для функции определённой лямбда-выражением.

(lambda lambda-list {declaration | doc-string}* {form}* )

определяется в лексическом окружении, в котором выполнялась форма defun. А так как формы defun обычно выполняются на самом верхнем уровне, лямбда-выражение обычно выполняется в нулевом лексическом окружении.

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

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

(defun (setf cadr) ...)

Это удобнее, чем использование defsetf или define-modify-macro.

Если указана необязательная строка документации doc-string, тогда она присоединяется к символу name в качестве строки документации типа function. Смотрите documentation. Если после doc-string нет деклараций, строка документации может быть использована только при условии существования хотя бы одной формы после неё, иначе она будет использована в качестве форм form функции. Указывать более чем одну строку doc-string является ошибкой.

Формы forms составляют тело определяемой функции. Они выполняются как неявный progn.

Тело определяемой функции неявно заключается в конструкцию block, имя которой совпадает с именем (name) функции. Таким образом для выхода из функции может быть использовано выражениеreturn-from.

В некоторых реализациях в defun могут также выполняться другие специальные учётные действия. name возвращается в качестве значения формы defun. Например:

(defun discriminant (a b c)
  (declare (number a b c))
  "Вычисляет дискриминант квадратного уравнения.
   Получает a, b и c и если они являются действительными числами (не комплексными),
    вычисляет значение b̂2-4*a*c.
   Квадратное уравнение a*x̂2+b*x+c=0 имеет действительные, multiple,
   или комплексные корни в зависимости от того, какое соответственно значение было получено
   положительное, ноль или отрицательное."
  (- (* b b) (* 4 a c)))
    discriminant
   теперь (discriminant 1 2/3 -2)  76/9

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


5.3.2 Определение глобальных переменных и констант

Для определения глобальных переменных используются операторы defvar и defparameter. Для определения констант используется оператор defconstant.

[Макрос] defvar name [initial-value [documentation]]

[Макрос] defparameter name initial-value [documentation]

[Макрос] defconstant name initial-value [documentation]

defvar рекомендуется для декларации использования в программе специальных переменных.

(defvar variable)

указывает на то, что переменная variable будет специальной (special) (смотрите proclaim), и может выполнять некоторые учётные действия, зависимые от реализации.

Если initial-value не было указано, defvar не изменяет значение переменной variable. Если initial-value не было указано и переменная не имела значения, defvar не устанавливает значение.

Если для формы указан второй аргумент,

(defvar variable initial-value)

тогда переменная variable, если она ещё не была проинициализирована, инициализируется результатом выполнения формы initial-value. Форма initial-value не выполняется, если в этом нет необходимости. Это полезно, если форма initial-value выполняет что-то трудоёмкое, как, например, создание большой структуры данных.

Если не существует специального связывания этой переменной, инициализация производится присвоением глобального значения переменной. Обычно, такого связывания быть не должно. FIXME.

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

(defvar *visible-windows* 0
  "Количество видимых окон на экране")

defparameter подобна defvar, но defparameter требует обязательной формы initial-value, и, выполняя эту форму присваивает результат переменной. Семантическое различие заключается в том, что defvar предназначена декларировать переменную, изменяемую программой, тогда как defparameter предназначена для декларации переменной, как константы, которая может быть изменена (и во время выполнения программы), для изменения поведения программы. Таким образом defparameter не указывает, что количество никогда не изменяется, в частности, она не разрешает компилятору предположить то, что значение может быть вкомпилировано в программу.

defconstant похожа на defparameter, но в отличие от последней, указывает, что значение переменной name фиксировано и позволяет компилятору предположить, что значение может быть вкомпилировано в программу. Однако, если компилятор для оптимизации выбирает путь замены ссылок на имя константы на значения этой константы в компилируемом коде, он должен позаботиться о том, чтобы такие «копии» были эквивалентны eql объектам-значениям констант. Например, компилятор может спокойно копировать числа, но должен позаботиться об этом правиле, если значение константы является списком.

Если для переменной на момент вызова формы defconstant существует специальные связывания, то возникает ошибка (но реализации могут проверять, а могут и не проверять этот факт).

Если имя задекларировано с помощью defconstant, последующие присваивания и связывания данной специальной переменной будут являться ошибкой. Это справедливо для системозависимых констант, например, t и most-positive-fixnum. Компилятор может также сигнализировать о связывании лексической переменной с одинаковым именем.

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

documentation-string не выполняется и должна представлять строку, когда выполняется defvar, defparameter или defconstant.

Например, форма

(defvar *avoid-registers* nil "Compilation control switch #43")

законна, но

(defvar *avoid-registers* nil
  (format nil "Compilation control switch #~D"
          (incf *compiler-switch-number*)))

ошибочна, так как вызов format не является дословно строкой.

С другой стороны, форма

(defvar *avoid-registers* nil
  #.(format nil "Compilation control switch #~D"
            (incf *compiler-switch-number*)))

может использоваться для вышеназванной цели, потому что вызов format выполняется на во время чтения кода read, когда форма defvar выполняется, в ней указана строка, которая являлась результатом вызова format.

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


5.3.3 Контроль времени выполнения

[Специальный оператор] eval-when ({situation}*) {form}*

Тело формы eval-when выполняется как неявный progn, но только в перечисленных ниже ситуациях. Каждая ситуация situation должна быть одним символов, :compile-toplevel, :load-toplevel или :execute.

Использование :compile-toplevel и :load-toplevel контролирует, что и когда выполняется для форм верхнего уровня. Использование :execute контролирует будет ли производится выполнения форм не верхнего уровня.

Конструкция eval-when может быть более понятна в терминах модели того, как компилятор файлов, compile-file, выполняет формы в файле для компиляции.

Формы следующие друг за другом читаются из файла с помощью компилятора файла используя read. Эти формы верхнего уровня обычно обрабатываются в том, что мы называем режим «времени некомпиляции (not-compile-time mode)». Существует и другой режим, называемый режим «времени-компиляции (compile-time-too mode)», которые вступает в игру для форм верхнего уровня. Оператор eval-when используется выбора режима(ов), в котором происходит выполнение кода.

Обработка форм верхнего уровня в компиляторе файла работает так, как рассказано ниже:

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

Для формы eval-when, которая не является формой верхнего уровня в компиляторе файлов (то есть либо в интерпретаторе, либо compile, либо в компиляторе файлов, но не на верхнем уровне), если указана ситуация :execute, тело формы обрабатывается как неявный progn. В противном случае, тело игнорируется и форма eval-when имеет значение nil.

Для сохранения обратной совместимости, situation может также быть compile, load или eval. Внутри формы верхнего уровня eval-when, они имеют значения :compile-toplevel, :load-toplevel и :execute соответственно. Однако их поведение не определено при использовании в eval-when не верхнего уровня.

Следующие правила являются логическим продолжением предыдущих определений:

Вот несколько дополнительных примеров.

(let ((x 1))
  (eval-when (:execute :load-toplevel :compile-toplevel)
    (setf (symbol-function ’foo1) #’(lambda () x))))

eval-when в предыдущем выражении не является формой верхнего уровня, таким образом во внимание берётся только ключевой символ :execute. это не будет иметь эффекта во время компиляции. Однако этот код установит в (symbol-function ’foo1) функцию которая возвращает 1 во время загрузки (если let форма верхнего уровня) или во время выполнения (если форма let вложена в какую-либо другую форму, которая ещё не была выполнена).

(eval-when (:execute :load-toplevel :compile-toplevel)
  (let ((x 2))
    (eval-when (:execute :load-toplevel :compile-toplevel)
      (setf (symbol-function ’foo2) #’(lambda () x)))))

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

(eval-when (:execute :load-toplevel :compile-toplevel)
  (setf (symbol-function ’foo3) #’(lambda () 3)))

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

(eval-when (:compile-toplevel)
  (eval-when (:compile-toplevel)
    (print ’foo4)))

Предыдущее выражение ничего не делает, оно просто возвращает nil.

(eval-when (:compile-toplevel)
  (eval-when (:execute)
    (print ’foo5)))

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

(eval-when (:execute :load-toplevel)
  (eval-when (:compile-toplevel)
    (print ’foo6)))

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