Common Lisp Backquote/Backtick:如何使用?

sad*_*tsu 9 lisp macros common-lisp backticks

我在使用Lisp的反引用读取宏时遇到问题.每当我尝试编写一个似乎需要使用嵌入式反引号的宏时(例如,``(w ,x ,,y)来自Paul Graham的ANSI Common Lisp,第399页),我无法弄清楚如何以编译的方式编写代码.通常情况下,我的代码会收到一系列错误,前面是"逗号不在反引号内".有人可以为我如何编写能够正确评估的代码提供一些指导吗?

作为一个例子,我目前需要一个宏,它采用一种描述形式的规则的形式,'(function-name column-index value)并生成一个谓词lambda体,以确定由column-index特定行索引的元素是否满足规则.如果我用规则调用这个宏'(< 1 2),我希望生成一个看起来如下的lambda体:

(lambda (row)
  (< (svref row 1) 2))
Run Code Online (Sandbox Code Playgroud)

我能做的最好的努力如下:

(defmacro row-satisfies-rule (rule)
  (let ((x (gensym)))
    `(let ((,x ,rule))
       (lambda (row)
         (`,(car ,x) (svref row `,(cadr ,x)) `,(caddr ,x))))))
Run Code Online (Sandbox Code Playgroud)

经过评估,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}>
Run Code Online (Sandbox Code Playgroud)

如何编写宏来生成我需要的代码,特别是如何实现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))))))
Run Code Online (Sandbox Code Playgroud)

此外,非常感谢使用干净,Lispy方法获取宏以生成代码以在运行时正确评估参数的解释.

sma*_*ane 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)))
Run Code Online (Sandbox Code Playgroud)

这扩展了你想要的方式:

(macroexpand-1 '(row-satisfies-rule (< 1 2)))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!


如果您需要评估参数以获取规则集,那么这是一个很好的方法:

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) (eval rule)
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))
Run Code Online (Sandbox Code Playgroud)

这是一个例子:

(let ((rules '((< 1 2) (> 3 4))))
  (macroexpand-1 '(row-satisfies-rule (car rules))))
=> (LAMBDA (ROW) (< (SVREF ROW 1) 2))
Run Code Online (Sandbox Code Playgroud)

就像以前那样.


如果你想要包含row在宏中,并让它直接给你答案,而不是做一个函数来做到这一点,试试这个:

(defmacro row-satisfies-rule-p (row rule)
  (destructuring-bind (function-name column-index value) rule
    `(,function-name (svref ,row ,column-index) ,value)))
Run Code Online (Sandbox Code Playgroud)

或者如果你需要评估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)))
Run Code Online (Sandbox Code Playgroud)

与宏相同的工作方式和更整洁,没有所有后悔的混乱担心.

一般来说,将宏用于可以通过函数完成的事情是不好的Lisp风格.


Rai*_*wig 5

要理解的一点是,反引号功能与宏完全无关.它可以用于列表创建.由于源代码通常由列表组成,因此它可能在宏中很方便.

CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)
Run Code Online (Sandbox Code Playgroud)

反引用引入了引用列表.逗号执行取消引用:评估逗号后的表达式并插入结果.逗号属于反引号:逗号仅在反引号表达式中有效.

另请注意,这是Lisp阅读器的严格特征.

以上基本类似于:

CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)
Run Code Online (Sandbox Code Playgroud)

这将创建一个包含第一个表达式(未计算,因为引用)和第二个表达式结果的新列表.

为什么Lisp提供反引号表示法?

因为当想要创建大多数元素未被评估的列表时,它提供了一个简单的模板机制,但有一些是.此外,反引号列表看起来类似于结果列表.