9

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

例如,我目前需要一个宏,它采用一种描述规则的形式,'(function-name column-index value)并生成一个谓词 lambda 主体,以确定column-index特定行的索引元素是否满足规则。如果我用 rule 调用这个宏'(< 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 和 discipulus 的想法,我修改了宏,使其可以编译和工作,甚至允许将表单作为参数传递。它的运行方式与我最初计划的宏有点不同,因为我确定将其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 方法来获取宏以生成代码以在运行时正确评估参数。

4

3 回答 3

12

首先,Lisp 宏具有“解构”参数列表。这是一个很好的功能,这意味着您可以简单地制作参数列表,而不是拥有一个参数列表(rule)然后将其拆开。这样,宏需要一个包含三个元素的列表作为参数,然后列表的每个元素都绑定到argumentmnt 列表中的相应符号。您可以使用或不使用它,但它通常更方便。(car rule) (cadr rule) (caddr rule)((function-name column-index value))

接下来,`,实际上并没有做任何事情,因为反引号告诉 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 风格。

于 2011-06-25T00:58:45.447 回答
6

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

CL-USER 4 > `((+ 1 2) ,(+ 2 3))
((+ 1 2) 5)

反引号引入了引用列表。逗号取消引用:计算逗号后的表达式并插入结果。逗号属于反引号:逗号仅在反引号表达式中有效。

另请注意,这严格来说是 Lisp 阅读器的一个特性。

上面基本类似:

CL-USER 5 > (list '(+ 1 2) (+ 2 3))
((+ 1 2) 5)

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

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

因为它提供了一种简单的模板机制,当人们想要创建大多数元素不被评估但少数元素被评估的列表时。此外,反引号列表看起来类似于结果列表。

于 2011-06-25T16:42:38.013 回答
4

你不需要嵌套的反引号来解决这个问题。此外,当它是一个宏时,您不必引用您的论点。(row-satisfies-rule (< 1 2))比 lispier 也是如此(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))))

将解决第一种形式的所有呼叫的问题。在第二种形式中解决问题留作练习。

于 2011-06-25T00:26:47.993 回答