Mic*_*ald 1 time common-lisp lisp-macros
我目前正在学习格雷厄姆的书"ANSI Common Lisp"中的lisp,作为练习,我正在编写基于Julian-day的日历计算.如您所知,复活节星期日每年都在变化,大约有10个特殊日子,实际日期取决于复活节星期日.
我想为每一天定义一个函数,所有函数都遵循以下模式:
(defun carnaval (year)
"Carnaval Monday of YEAR.
This is 48 days before Easter Sunday."
(- (easter year) 48))
Run Code Online (Sandbox Code Playgroud)
使用宏似乎是合适的,而不是重复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)))
Run Code Online (Sandbox Code Playgroud)
(起始%标记我的意图是不将宏导出到定义它的包中.)
该宏可用于为日期基于东方星期日的每一天定义一个函数:
(%defeasterday carnaval "Carnaval Monday" -48)
(%defeasterday mardi-gras "Mardi gras" -47)
(%defeasterday ash "Ash Wednesday" -46)
…
Run Code Online (Sandbox Code Playgroud)
现在,为了练习,我想将所有数据打包到列表中并%defeasterday在其项目上使用宏.我的尝试是
(mapc #'(lambda (args) (apply #'%defeasterday args))
'((carneval "Carneval Monday" -48)
(mardi-gras "Mardi gras" -47)
(ash "Ash Wednesday" -46)))
Run Code Online (Sandbox Code Playgroud)
这失败了
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]
Run Code Online (Sandbox Code Playgroud)
它告诉我一个宏不是"只是一个函数映射代码代码",因为它apply是挑剔运行它们.
如何使用%defeasterday上面的宏来迭代列表?
(如果您需要一个特殊easter功能用于测试,请(defun easter () 2457860)提供2017年的预期答案.)
应用宏不起作用
你不能应用宏:
(apply #'defsomething '(foo bar))
Run Code Online (Sandbox Code Playgroud)
但是你可以评估一个表格:
(eval (let ((args '(foo bar)))
`(defsomething ,@args)))
Run Code Online (Sandbox Code Playgroud)
要么
(let ((args '(foo bar)))
(eval `(defsomething ,@args)))
Run Code Online (Sandbox Code Playgroud)
COMPILE如果要确保编译代码,也请参阅该函数.
使用定义宏
使用定义(!)宏的正确方法是:
(%defeasterdays
(carnaval "Carnaval Monday" -48)
(mardi-gras "Mardi gras" -47)
(ash "Ash Wednesday" -46))
Run Code Online (Sandbox Code Playgroud)
然后%defeasterdays宏应该扩展到:
(progn
(%defeasterday carnaval "Carnaval Monday" -48)
(%defeasterday mardi-gras "Mardi gras" -47)
(%defeasterday ash "Ash Wednesday" -46))
Run Code Online (Sandbox Code Playgroud)
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*)
Run Code Online (Sandbox Code Playgroud)
主要优点是文件编译器将在编译时看到所有生成函数的定义.它将能够为函数生成有效的代码,并且还可以为调用这些函数的表单生成有效的代码.
您也可以加载此文件,但这取决于实现是否将编译代码或最终是否有解释函数.