定义名称基于宏参数的函数的宏

Posted

技术标签:

【中文标题】定义名称基于宏参数的函数的宏【英文标题】:Macro that defines functions whose names are based on the macro's arguments 【发布时间】:2015-08-05 14:05:19 【问题描述】:

*注意:尽管很长时间都经常光顾 ***,但这是我自己发布的第一个问题。抱歉,如果它有点冗长。建设性的批评表示赞赏。

当我在 Common Lisp 中使用 defstruct 定义一个结构时,会自动生成一个谓词函数来测试它的参数是否属于 defstruct 定义的类型。例如:

(defstruct book
  title
  author)

(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain")))
  (book-p huck-finn))
=> True

但是,当使用 defclass 定义一个类时,默认情况下似乎不会生成这样的函数(有没有办法指定这个?),所以我试图自己添加这个功能,因为我想要 a)这种语法在结构和类之间是一致的,b) 有一个 (typep obj 'classname) 的缩写,我需要经常写它并且在视觉上很嘈杂, c) 作为一个编程练习,因为我对 Lisp 还比较陌生。

我可以编写一个宏来定义一个给定类名的谓词函数:

(defclass book ()
  ((title :initarg :title
          :accessor title)
   (author :initarg :author
           :accessor author)))

;This...
(defmacro gen-predicate (classname)
  ...)

;...should expand to this...
(defun book-p (obj)
  (typep obj 'book))

;...when called like this:
(gen-predicate 'book)

我需要传递给 defun 的名称必须是 'classname-p 的形式。这就是我遇到困难的地方。要创建这样一个符号,我可以使用 Paul Graham 的 On Lisp(第 58 页)中的“symb”函数。在 REPL 上运行时:

(symb 'book '-p)
=> BOOK-P

到目前为止,我的 gen-predicate 宏看起来像这样:

(defmacro gen-predicate (classname)
  `(defun ,(symb classname '-p) (obj)
     (typep obj ,classname)))

(macroexpand `(gen-predicate 'book))
=>
(PROGN
 (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN '|'BOOK-P| 'NIL T))
 (SB-IMPL::%DEFUN '|'BOOK-P|
                  (SB-INT:NAMED-LAMBDA |'BOOK-P|
                      (OBJ)
                    (BLOCK |'BOOK-P| (TYPEP OBJ 'BOOK)))
                  NIL 'NIL (SB-C:SOURCE-LOCATION)))
T

看起来(symb 'book '-p) 创建的符号实际上被实现(SBCL)视为|'BOOK-P|,而不是BOOK-P。果然,现在可以了:

(let ((huck-finn (make-instance 'book)))
  (|'BOOK-P| huck-finn))
=> True

为什么 symb 创建的符号被实习生为|'BOOK-P|?在 On Lisp(与上面相同的页面)中,Graham 说:“任何字符串都可以是符号的打印名称,甚至是包含小写字母或括号等宏字符的字符串。当符号名称包含这种奇怪的东西时,它会在垂直方向打印酒吧。”在这种情况下不存在这样的怪事,是吗?我是否认为符号的“打印名称”是打印符号时标准输出上实际显示的内容,并且在这种奇怪的情况下,与符号本身的形式不同?

能够编写像gen-predicate 这样的函数定义宏——其定义的函数是根据传递给宏的参数命名的——在我看来,这就像 Lisp 黑客可能已经做了很多年的事情。用户 Kaz 在这里 (Merging symbols in common lisp) 说,通常可以避免符号的“混搭”,但这会破坏这个宏的目的。

最后,假设我可以让gen-predicate 以我想要的方式工作,那么确保为每个定义的新类调用它的最佳方法是什么?与initialize-instance 可以自定义以在类的实例化 上执行某些操作的方式非常相似,是否有一个由defclass 调用的通用函数可以根据定义 执行操作一个班级?

谢谢。

【问题讨论】:

【参考方案1】:

这是一个常见的问题:传递给宏的是什么?

像这样比较调用:

(symb 'book '-p)

(symb ''book '-p)

你的宏形式是这样的:

(gen-predicate 'book)

GEN-PREDICATE 是一个宏。 classname 是这个宏的参数。

现在在代码扩展期间,宏内部的classname 的值是多少?是book 还是'book

其实是后者,因为你写了(gen-predicate 'book)。请记住:宏查看源代码,参数源被传递给宏函数 - 而不是值。参数是'book。这样就通过了。 (QUOTE BOOK) 相同,只是打印方式不同。所以它是一个包含两个元素的列表。第一个元素是符号QUOTE,第二个元素是符号BOOK

因此,宏现在使用参数值 (QUOTE BOOK) 或更短的 'BOOK 调用函数 SYMB

如果要生成不带引号字符的谓词,需要写:

(gen-predicate book)

或者您也可以更改宏:

(symb classname '-p)

应该是:

(symbol (if (and (consp classname)
                 (eq (first classname) 'quote))
           (second classname)
           classname))

比较

我们写

(defun foo () 'bar)

而不是

(defun 'foo () 'bar)    ; note the quoted FOO

DEFUN 是一个宏,第一个参数是函数名。那是一个类似的问题......

问题的第二部分

我真的不知道有什么好的答案。我不记得在类定义之后运行代码(例如定义函数)的任何简单方法。

也许可以使用 MOP,但这很丑。

编写一个自定义宏 DEFINE-CLASS,它可以满足您的需求:扩展为 DEFCLASSDEFUN

遍历包中的所有符号,找到类并定义相应的谓词

【讨论】:

当然,太棒了!我不知何故不认为'book 真的是(quote book)。我理解您的第二个解决方案,但第一个解决方案令人困惑。我实际上已经意识到 (macroexpand (gen-predicate book) expands into what I wanted, but since the symbol book` 没有被引用,那么它被解释为一个未定义的变量。我不知道如何避免这个问题。感谢您的及时和乐于助人的回复! 这个答案很好地解决了问题的第一部分。还有第二部分,我很想看到解决:如何在创建新类时始终调用它? (和/或什么时候愿意或不愿意这样做?)【参考方案2】:

为了解决问题的第二部分,类本身就是对象,这要归功于 MOP,因此可能可以在专门针对 STANDARD-CLASS 的 initialize-instance 上编写一个 :after 方法。但是你应该检查MOP,看看是否允许定义这样的方法。

如果可能,那么可以,您可以运行代码来响应类的创建;但是,由于您直到运行时才知道正在创建的类的名称,因此您无法在源代码中以文本方式拼写它,因此您无法使用宏(除非您使用 eval)。你宁愿使用类似的东西

(let ((classname (class-name class)))
  (compile (generate-my-predicate-symbol classname)
    (lambda (x) (typep x classname))))

我认为 Rainer 建议编写自己的 DEFINE-CLASS 宏是可行的方法,我的意思是,如果没有任何其他考虑因素,经验丰富的 Lisper 很可能会这样做。但我并不是一个经验丰富的 Lisper,所以我可能错了 ;)

【讨论】:

以上是关于定义名称基于宏参数的函数的宏的主要内容,如果未能解决你的问题,请参考以下文章

c/c++宏函数的定义与使用(宏定义函数)(macro definition)

c语言宏定义有无参数的区别

帮忙看下这个带参的宏定义,看不懂?

C语言带参数的宏定义:定义宏名时可以不写出参数???

C/C++宏定义可不可以传入参数?

Rust 1.7.0的macro宏-语法分析和使用举例