Использование макроса для определения функций, вычисляющих 10 дат, связанных с Пасхой
В настоящее время я изучаю язык с книгой Грэма "ANSI Common Lisp" и в качестве упражнения пишу вычисления календаря на основе юлианского дня. Как вы знаете, пасхальное воскресенье меняется от года к году, и существует еще около 10 специальных дней, фактическая дата которых зависит от даты пасхального воскресенья.
Я хочу определить функцию для каждого из этих дней, следуя следующей схеме:
(defun carnaval (year)
"Carnaval Monday of YEAR.
This is 48 days before Easter Sunday."
(- (easter year) 48))
Вместо 10-кратного повторения аналогичных объявлений представляется целесообразным использовать макрос:
(defmacro %defeasterday (function-name screen-name offset)
`(defun ,function-name (year)
,(format nil "~A of YEAR.~%~%This is ~A day~:p ~A Easter Sunday"
screen-name
(abs offset)
(if (< 0 offset) "after" "before"))
(+ (easter year) ,offset)))
(Стартовый %
помечает мое намерение не экспортировать макрос в пакет, где он определен.)
Макрос может использоваться для определения функции для каждого дня, дата которого основана на дате восточного воскресенья:
(%defeasterday carnaval "Carnaval Monday" -48)
(%defeasterday mardi-gras "Mardi gras" -47)
(%defeasterday ash "Ash Wednesday" -46)
…
Теперь ради упражнения я хотел бы упаковать все данные в список и использовать %defeasterday
макрос на свои предметы. Моя попытка была
(mapc #'(lambda (args) (apply #'%defeasterday args))
'((carneval "Carneval Monday" -48)
(mardi-gras "Mardi gras" -47)
(ash "Ash Wednesday" -46)))
Это не с
Execution of a form compiled with errors.
Form:
#'%DEFEASTERDAY
Compile-time error:
The :macro name %DEFEASTERDAY was found as the argument to FUNCTION.
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
который учит меня тому, что макрос - это не просто код преобразования функции в код, поскольку apply
разборчив в управлении ими.
Как я могу использовать %defeasterday
макрос выше, чтобы перебрать список?
(Если вам нужен специальный easter
функция для тестирования, пожалуйста (defun easter () 2457860)
который дает ожидаемый ответ на 2017 год.)
1 ответ
Применение макроса не работает
Вы не можете применить макрос:
(apply #'defsomething '(foo bar))
Но вы можете оценить форму:
(eval (let ((args '(foo bar)))
`(defsomething ,@args)))
или же
(let ((args '(foo bar)))
(eval `(defsomething ,@args)))
Также см. Функцию COMPILE
, если вы хотите убедиться, что код скомпилирован.
Использование определяющего макроса
Правильный способ использования определяющего (!) Макроса таков:
(%defeasterdays
(carnaval "Carnaval Monday" -48)
(mardi-gras "Mardi gras" -47)
(ash "Ash Wednesday" -46))
%defeasterdays
макрос должен расшириться выше в:
(progn
(%defeasterday carnaval "Carnaval Monday" -48)
(%defeasterday mardi-gras "Mardi gras" -47)
(%defeasterday ash "Ash Wednesday" -46))
DEFUN
это макрос верхнего уровня. Когда-то обычно хочется, чтобы так было. Если вы используете EVAL
из DEFUN
форма, то это не на верхнем уровне для файлового компилятора. Таким образом, вам нужно выполнить преобразование в макросе так, чтобы определяющие формы все еще находились на "верхнем уровне". PROGN
подчиненные формы все еще находятся на верхнем уровне для файлового компилятора.
Осчастливить файловый компилятор
Вы можете использовать файловый компилятор для компиляции следующего кода:
; we need the file compiler to know the value of *DAYS*
; thus the eval-when. By default `DEFVAR` would not have
; been executed
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *days*
'((carnaval "Carnaval Monday" -48)
(mardi-gras "Mardi gras" -47)
(ash "Ash Wednesday" -46))))
; a file compiler sees the following macro
: and its definition is automatically available at compile time
(defmacro %defeasterday (function-name screen-name offset)
`(defun ,function-name (year)
,(format nil "~A of YEAR.~%~%This is ~A day~:p ~A Easter Sunday"
screen-name
(abs offset)
(if (< 0 offset) "after" "before"))
(+ (easter year) ,offset)))
; same here, the compiler learns about the next macro
(defmacro %defeasterdays (list)
`(progn ,@(loop for item in (symbol-value list)
collect `(%defeasterday ,@item))))
; now the file compiler sees the following form.
; it will be macro expanded. The macros are known.
; one of the macros now needs at expansion time the value
; of *DAYS*. Since we have made *DAYS* known to the compiler,
; this will work.
(%defeasterdays *days*)
Основным преимуществом является то, что файловый компилятор увидит определение всех ваших сгенерированных функций во время компиляции. Он сможет генерировать эффективный код для функций, а также может генерировать эффективный код для форм, вызывающих эти функции.
Вы также можете загрузить этот файл, но это зависит от реализации, будет ли код скомпилирован или вы получите интерпретированные функции.