本文介绍了常用Lisp反引号/反引号:如何使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用Lisp的反引号读宏时遇到了问题。每当我试图编写似乎需要使用嵌入式反引号的宏时(例如,Paul Graham的ANSI Common Lisp第399页中的``(w ,x ,,y)),我都不知道如何以编译的方式编写代码。通常,我的代码会收到前面带有"逗号不在反引号内"的整个错误链。有人能为我如何编写正确计算的代码提供一些指导吗?

作为示例,我目前需要一个宏来描述'(function-name column-index value)形式的规则,并生成一个谓词lambda body来确定由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))))))

另外,如果您能解释一下让宏生成代码以便在运行时正确计算参数的干净、灵活的方法,我们将不胜感激。

推荐答案

首先,LIP宏有"解构"参数列表。这是一个很好的特性,这意味着不需要有一个参数列表(rule),然后用(car rule) (cadr rule) (caddr rule)拆分它,只需使参数列表((function-name column-index value))即可。这样,宏会将一个包含三个元素的列表作为参数,然后将该列表中的每个元素绑定到araremnt列表中的相应符号。您可以使用或不使用此选项,但它通常更方便。

Next,`,实际上不做任何事情,因为反引号告诉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风格。

这篇关于常用Lisp反引号/反引号:如何使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-30 23:05