我在使用Lisp的反引用读取宏时遇到问题。每当我尝试编写一个似乎需要使用嵌入式反引号的宏时(例如,来自Paul Graham的 ANSI Common Lisp 的``(w ,x ,,y)
,第399页),我无法弄清楚如何编写我的以编译方式编写代码。通常情况下,我的代码会收到一系列错误,前面是“逗号不在反引号内”。有人可以为我如何编写能够正确评估的代码提供一些指导吗?
作为一个例子,我目前需要一个宏,它采用一种形式来描述'(function-name column-index value)
形式的规则,并生成一个谓词lambda主体,以确定由column-index
索引的元素是否为特定的行满足规则。如果我使用规则'(< 1 2)
调用此宏,我希望生成一个如下所示的lambda体:
(lambda (row)
(< (svref row 1) 2))
我能做的最好的努力如下:
(defmacro row-satisfies-rule (rule)
(let ((x (gensym)))
`(let ((,x ,rule))
(lambda (row)
(`,(car ,x) (svref row `,(cadr ,x)) `,(caddr ,x))))))
经过评估,SBCL会发出以下错误报告:
; in: ROW-SATISFIES-RULE '(< 1 2)
; ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))
;
; caught ERROR:
; illegal function call
; (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
; ==>
; #'(LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121)))
;
; caught STYLE-WARNING:
; The variable ROW is defined but never used.
; (LET ((#:G1121 '(< 1 2)))
; (LAMBDA (ROW) ((CAR #:G1121) (SVREF ROW (CADR #:G1121)) (CADDR #:G1121))))
;
; caught STYLE-WARNING:
; The variable #:G1121 is defined but never used.
;
; compilation unit finished
; caught 1 ERROR condition
; caught 2 STYLE-WARNING conditions
#<FUNCTION (LAMBDA (ROW)) {2497F245}>
如何编写宏来生成我需要的代码,特别是如何实现row-satisfies-rule
?
使用来自Ivijay和纪律的想法,我修改了宏以便它编译和工作,甚至允许将表单作为参数传递。它与我原先计划的宏运行方式略有不同,因为我确定包含row
作为参数使代码更平滑。然而,它像罪一样丑陋。有没有人知道如何清理它,所以它执行相同而不调用eval
?
(defmacro row-satisfies-rule-p (row rule)
(let ((x (gensym))
(y (gensym)))
`(let ((,x ,row)
(,y ,rule))
(destructuring-bind (a b c) ,y
(eval `(,a (svref ,,x ,b) ,c))))))
此外,我们将非常感谢有关获取宏以生成代码以在运行时正确评估参数的干净,Lispy方法的解释。
答案 0 :(得分:12)
首先,Lisp宏有“解构”参数列表。这是一个很好的功能,这意味着不是使用参数列表(rule)
,而是将其与(car rule) (cadr rule) (caddr rule)
分开,您可以简单地创建参数列表((function-name column-index value))
。这样宏就会将三个元素的列表作为参数,然后将列表中的每个元素绑定到arguemnt列表中的相应符号。你可以使用或不使用,但通常更方便。
接下来,`,
实际上没有做任何事情,因为反引用告诉Lisp不要评估下面的表达式,并且逗号告诉它毕竟评估它。我认为你的意思是,(car x)
,它评估(car x)
。如果你使用解构参数,这不是问题。
由于你没有在宏扩展中引入任何新变量,我认为在这种情况下我不认为(gensym)
。
所以我们可以像这样重写宏:
(defmacro row-satisfies-rule ((function-name column-index value))
`(lambda (row)
(,function-name (svref row ,column-index) ,value)))
这扩展了你想要的方式:
(macroexpand-1 '(row-satisfies-rule (< 1 2)))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))
希望这有帮助!
如果您需要评估参数以获取规则集,那么这是一个很好的方法:
(defmacro row-satisfies-rule (rule)
(destructuring-bind (function-name column-index value) (eval rule)
`(lambda (row)
(,function-name (svref row ,column-index) ,value))))
以下是一个例子:
(let ((rules '((< 1 2) (> 3 4))))
(macroexpand-1 '(row-satisfies-rule (car rules))))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))
就像以前一样。
如果你想在宏中包含row
并让它直接给你答案而不是做一个功能,试试这个:
(defmacro row-satisfies-rule-p (row rule)
(destructuring-bind (function-name column-index value) rule
`(,function-name (svref ,row ,column-index) ,value)))
或者,如果您需要评估rule
参数(例如,传递'(< 1 2)
或(car rules)
而不是(< 1 2)
),那么只需使用(destructuring-bind (function-name column-index value) (eval rule)
实际上,对于您正在尝试执行的操作,函数似乎比宏更合适。简单地
(defun row-satisfies-rule-p (row rule)
(destructuring-bind (function-name column-index value) rule
(funcall function-name (svref row column-index) value)))
与宏的工作方式相同,而且更整洁,没有所有后悔的麻烦。
一般来说,将宏用于可由函数完成的事情是不好的Lisp风格。
答案 1 :(得分:5)
要理解的一点是,反引号功能与宏完全无关。它可以用于列表创建。由于源代码通常由列表组成,因此在宏中可能很方便。
CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)
反引用引入了引用列表。逗号执行取消引用:评估逗号后的表达式并插入结果。逗号属于反引号:逗号仅在反引号表达式中有效。
另请注意,这绝对是Lisp阅读器的一项功能。
以上基本类似于:
CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)
这将创建一个包含第一个表达式(未计算,因为引用)和第二个表达式结果的新列表。
为什么Lisp会提供反引号表示法?
因为当想要创建大多数元素未被评估的列表时,它提供了一个简单的模板机制,但有一些是。此外,反引号列表看起来与结果列表类似。
答案 2 :(得分:4)
您不需要嵌套反引用来解决此问题。此外,当它是一个宏时,你不必引用你的论点。因此(row-satisfies-rule (< 1 2))
比(row-satisfies-rule '(< 1 2))
更加轻松。
(defmacro row-satisfies-rule (rule)
(destructuring-bind (function-name column-index value) rule
`(lambda (row)
(,function-name (svref row ,column-index) ,value))))
将解决第一种形式的所有呼叫的问题。在第二种形式中解决问题留作练习。