Ответ 1
Компиляция файла Lisp
Возьмем, например, компиляцию файла Lisp. Компилятор Lisp обрабатывает формы верхнего уровня. Это могут быть произвольные формы Lisp, DEFUN, DEFMACROS, DEFCLASS, вызовы функций,...
Вся история, как работает компилятор файлов, слишком сложна для объяснения здесь, но несколько вещей:
-
компилятор файла генерирует код для формы
(DEFUN foo () )
. Но он не выполняет форму defun. Таким образом, во время компиляции известно, что существует функцияFOO
, но код "FOO" недоступен во время компиляции. Компилятор генерирует код для скомпилированного файла, но не сохраняет его в памяти. Вы не можете вызвать такую функцию во время компиляции. -
для макросов это несколько отличается:
(DEFMACRO BAZ ...)
. Компилятор файла не только скомпилирует макрос и заметит, что он есть, но также сделает макрос доступным во время компиляции. Он загружается в среду компилятора.
Итак, представьте себе последовательность форм в файле:
(defmacro baz ...)
(defun foo () (baz ...))
Это работает, потому что компилятор файла знает макрос BAZ
, и когда он компилирует код для FOO
, тогда он может развернуть форму макроса.
Теперь рассмотрим следующий пример:
(defun bar (form) ...)
(defmacro baz (form) (bar form))
(defun foo () (baz ...))
Выше не будет работать. Теперь макрос BAZ
использует функцию BAR
, вызывая его. Когда компилятор пытается скомпилировать функцию FOO
, он не может развернуть макрос BAZ
, потому что BAR
не может быть вызван, потому что код BAR
не загружается в среду компиляции.
Есть два решения:
- скомпилировать и загрузку
BAR
раньше, используя отдельный файл. - Используйте EVAL-WHEN
Пример для EVAL-WHEN
:
(eval-when (:compile-toplevel :execute :load-toplevel)
(defun bar (form) ...)
)
(defmacro baz (form) (bar form))
(defun foo () (baz ...))
Теперь EVAL-WHEN
инструктирует компилятор файла фактически запустить форму DEFUN во время компиляции. Эффект этого: компилятор файла теперь знает определение BAR
во время компиляции. Таким образом, он доступен позже, когда компилятор файла должен вызвать BAR
во время макрорасширения использования BAZ
.
Можно использовать только :compile-toplevel
, когда функция не понадобится после компиляции файла. Если он используется позже, нам нужно убедиться, что он загружен.
Итак, EVAL-WHEN
позволяет указать, должен ли выполняться конкретный фрагмент кода
- при компиляции файла
- при загрузке файла
- во время выполнения
EVAL-WHEN
не используется часто в коде пользователя. Если вы его используете, тогда вы должны спросить себя, действительно ли вам это нужно.