使用宏来定义计算10个与复活节相关的日期的函数

时间:2017-09-29 06:43:27

标签: time common-lisp lisp-macros

我目前正在学习与格雷厄姆的书“ANSI Common Lisp”进行口述,并且作为一项练习,我正在编写基于Julian-day的日历计算。如您所知,复活节星期日每年都在变化,大约有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)))

使用宏似乎是合适的,而不是重复10次类似的声明:

%

(起始(%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)功能进行测试,请include[]给出2017年的预期答案。)

1 个答案:

答案 0 :(得分:5)

应用宏不起作用

您无法应用宏:

(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*)

主要优点是文件编译器将在编译时看到所有生成函数的定义。它将能够为函数生成有效的代码,并且还可以为调用这些函数的表单生成有效的代码。

您也可以加载此文件,但这取决于实现代码是否将被编译或者您是否最终使用解释函数。