【发布时间】:2011-12-28 05:50:03
【问题描述】:
我最近开始使用 Lisp 进行编码,并且已经对宏印象深刻 - 它们在编译时 allowed me to do complex loop-unrolling,这是我无法用我所知道的任何其他语言优雅地做到这一点的(即代码-在保持原始结构的同时生成)。
关于优化:我在同一个代码中添加了类型注释(很多“fixnum”)。一旦我添加了 3 或 4 个,我意识到我做错了 - 这就是宏的用途,不要重复你自己......
; whenever we want to indicate that the result of an operation
; fits in a fixnum, we macro expand (the fixnum (...))
(defmacro fast (&rest args)
`(the fixnum ,args))
...
(cond
(...)
(t (let* ((forOrange (+ (aref counts 5)
(fast * 2 (aref counts 6))
(fast * 5 (aref counts 7))
(fast * 10 (aref counts 8))))
(forYellow (+ (aref counts 3)
(fast * 2 (aref counts 2))
(fast * 5 (aref counts 1))
(fast * 10 (aref counts 0))))
...确实,这行得通:我没有在各处写很多“(the fixnum (...))”,而是快速在表达式前面加上“fast” - 一切都很好。
然后……
我意识到即使这不是事情应该停止的地方:原则上,宏“快速”应该......在评估的顶部调用,在这种情况下:
(forYellow (fast + (aref counts 3)
(* 2 (aref counts 2))
(* 5 (aref counts 1))
(* 10 (aref counts 0))))
...并且它应该在所有子表达式中递归地“种植”“(the fixnum (...))”。
这可以吗? “defmacro”可以递归吗?
更新:我在尝试执行此操作时遇到了一些非常奇怪的问题,因此我最终按照 Rord 的建议执行了以下操作 - 即实现了一个函数,在 repl 中对其进行了测试,然后从宏中调用它:
(defun operation-p (x)
(or (equal x '+) (equal x '-) (equal x '*) (equal x '/)))
(defun clone (sexpr)
(cond
((listp sexpr)
(if (null sexpr)
()
(let ((hd (car sexpr))
(tl (cdr sexpr)))
(cond
((listp hd) (append (list (clone hd)) (clone tl)))
((operation-p hd) (list 'the 'fixnum (cons hd (clone tl))))
(t (cons hd (clone tl)))))))
(t sexpr)))
(defmacro fast (&rest sexpr)
`(,@(clone sexpr)))
它在 SBCL 下工作正常:
$ sbcl
This is SBCL 1.0.52, an implementation of ANSI Common Lisp.
...
* (load "score4.cl")
T
* (setf a '(+ (1 2) (- 1 (+ 5 6)))
...
* (clone a)
(THE FIXNUM (+ (1 2) (THE FIXNUM (- 1 (THE FIXNUM (+ 5 6))))))
* (macroexpand '(fast + 1 2 THE FIXNUM (- 1 THE FIXNUM (+ 5 6))))
(THE FIXNUM (+ 1 2 THE FIXNUM (THE FIXNUM (- 1 THE FIXNUM (THE FIXNUM (+ 5 6))))))
T
一切都很好,除了一个副作用:CMUCL 工作,但不再编译代码:
; Error: (during macroexpansion)
; Error in KERNEL:%COERCE-TO-FUNCTION: the function CLONE is undefined.
哦,好吧:-)
更新:编译失败在a different SO question中得到解决。
【问题讨论】:
-
一个风格建议:不要将要转换的表格拼接成宏调用。将其保留为子表单,即更喜欢
(fast (* ...))而不是(fast * ...)。它使语义更简单(考虑(fast 3)的情况——这个表单应该是什么意思?)并且迎合了读者对表单结构的期望。 -
@Matthias:最初我是这样的——但我意识到在这种情况下存在明确的语义(即“(fast 3)”只能表示“3”)并且......它更易于使用:您会在代码中看到要强制转换为 fixnum 的位置,您不必寻找paren-matching:只需在前面输入“fast”即可。
-
这在Scheme中很常见。
-
@leppie 对。它甚至有内置的先例,例如
map:(map bling xs)。但请注意,这不适用于 CL,您必须在其中编写(mapcar #'bling xs)。产生差异的原因是 Scheme 是一个 Lisp-1,所以对于bling的含义没有任何混淆。在 CL 中,当我看到bling处于非操作员位置时,我认为它是 变量bling。因此,我对我认为与读者期望相矛盾的内容提出警告。 -
@ttsiodras 在调用中包含表单时必须寻找括号意味着您没有使用足够好的编辑器。 ;) 使用 paredit,尝试
C-1 (。
标签: macros lisp common-lisp