【问题标题】:Common Lisp Backquote/Backtick: How to Use?Common Lisp 反引号/反引号:如何使用?
【发布时间】:2011-09-22 10:02:51
【问题描述】:

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

例如,我目前需要一个宏,它采用以'(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 和 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 方法以获取宏以生成代码以在运行时正确评估参数。

【问题讨论】:

  • gensyms 真的有必要吗?您没有在扩展中引入任何新变量,并且用于生成扩展的变量名称不会导致泄漏。
  • 去掉gensyms,去掉eval,然后就是三行了,好看,还行。你甚至可以把destructuring-bind去掉,变成两行。
  • 值得注意的是,事实上,宏甚至不是必需的;事实上,一个函数可能更合适。

标签: macros lisp common-lisp backticks


【解决方案1】:

您不需要嵌套反引号来解决这个问题。此外,当它是一个宏时,您不必引用您的论点。所以(row-satisfies-rule (&lt; 1 2))(row-satisfies-rule '(&lt; 1 2)) 更狡猾。

(defmacro row-satisfies-rule (rule)
  (destructuring-bind (function-name column-index value) rule
    `(lambda (row)
       (,function-name (svref row ,column-index) ,value))))

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

【讨论】:

    【解决方案2】:

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

    接下来,`, 实际上并没有做任何事情,因为反引号告诉 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 参数(例如,传递 '(&lt; 1 2)(car rules) 而不是 (&lt; 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 风格。

    【讨论】:

    • 当我的规则在列表中时尝试调用此宏,例如(row-satisfies-rule (car rules)),我收到一条错误消息,指出(car rules) 没有正确映射到三个变量。我该如何解决这个问题?
    • @gamecoder 问题是宏不评估它们的参数,因此它尝试将function-name 绑定到carcolumn-indexrulesvalue 没有任何东西,因此错误)。在这种情况下,我认为您不能使用解构参数列表,您必须使用单个参数并对其进行评估以获取规则集。我在我的答案中添加了一个很好的方法(这与 lvijay 在他的答案中使用的基本相同)。
    • @discuplus:我在哪里可以学习到好的 Lisp 风格?我大约六个月前才开始,我仍然摸索了很多。我的代码比较怪诞,我见过的 Lisp 例子也很漂亮:/
    • @gamecoder 好吧,我还不能声称完全理解好的 Lisp 风格(目前),但我从 Practical Common Lisp 和这里学到的关于 Lisp 的一切都是关于堆栈溢出的。我最好的建议是继续编写代码,如果它不起作用或看起来很奇怪,请寻求批评;正是你在这里做的。您将开始了解如何编写漂亮(且功能正常)的 Lisp。
    【解决方案3】:

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

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

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

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

    上面基本类似:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2011-11-24
      • 1970-01-01
      • 1970-01-01
      • 2011-06-02
      • 2015-04-30
      • 1970-01-01
      • 2021-08-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多