定义函数的宏,其名称基于宏的参数
Macro that defines functions whose names are based on the macro's arguments
*注意:尽管我经常访问 Whosebug 很长时间,但这是我自己发布的第一个问题。抱歉,如果它有点冗长。建设性的批评表示赞赏。
当我在 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 定义一个 class 时,这些函数似乎不是默认生成的(有没有办法指定这个?),所以我试图添加这个我自己的功能,因为我希望 a) 这种语法在结构和 classes 之间保持一致,b) 有一个 (typep obj 'classname)
的缩写,我需要经常写它并且在视觉上很吵,
和 c) 作为编程练习,因为我对 Lisp 还是比较陌生。
我可以编写一个宏来定义给定一个谓词函数的 class:
(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创建的symbol intern为|'BOOK-P|
?在 On Lisp(同上)中,Graham 说:"Any string can be the print-name of a symbol, even a string containing lowercase letters or macro characters like parentheses. When a symbol's name contains such oddities, it is printed within vertical bars." 在这种情况下不存在这样的奇怪之处,是吗?我是否正确地认为符号的 "print-name" 是打印符号时标准输出上实际显示的内容,并且在这种奇怪的情况下,与符号本身的形式不同?
能够编写像 gen-predicate
这样的函数定义宏——其定义的函数是根据传递给宏的参数命名的——在我看来就像 Lisp 黑客可能已经做了很多年了。用户 Kaz 在这里 (Merging symbols in common lisp) 说符号的 "mashing-up" 通常可以避免,但这会破坏这个宏的目的。
最后,假设我可以让 gen-predicate
以我想要的方式工作,那么什么是确保每个新的 class 都按定义调用它的最佳方法?与 initialize-instance
可以自定义以在 class 的 实例化 上执行某些操作的方式非常相似,是否存在由 def[=55= 调用的通用函数] 可以根据 class?
的 定义 执行操作
谢谢。
这是一个常见的问题:传递给宏的是什么?
像这样比较调用:
(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
是一个宏,第一个参数是函数名。这是一个类似的问题...
第二部分问题
我真的不知道有什么好的答案。我不记得在 class 定义之后 运行 编码(例如定义函数)的任何简单方法。
也许可以使用 MOP,但那太丑了。
写一个自定义宏 DEFINE-CLASS
做你想做的事:扩展成 DEFCLASS
和 DEFUN
.
遍历包中的所有符号,找到classes并定义相应的谓词
要解决问题的第二部分,classes 本身就是对象,多亏了 MOP,因此 可能 可以编写一个 :after 方法在专门针对 STANDARD-CLASS 的初始化实例上。但是你应该检查MOP,看看是否允许定义这样的方法。
如果可能的话,是的,您可以 运行 代码响应 class 的创建;但是,由于您直到 运行 时才知道正在创建的 class 的名称,因此您无法在源代码中以文本方式拼写它,因此您无法使用您的宏(除非您使用 eval)。你宁愿使用像
这样的东西
(let ((classname (class-name class)))
(compile (generate-my-predicate-symbol classname)
(lambda (x) (typep x classname))))
我认为 Rainer 关于编写自己的 DEFINE-CLASS 宏的建议是可行的方法,我的意思是,如果没有任何其他考虑,经验丰富的 Lisper 很可能会这样做玩。但我并不是一个经验丰富的 Lisper,所以我可能是错的 ;)
*注意:尽管我经常访问 Whosebug 很长时间,但这是我自己发布的第一个问题。抱歉,如果它有点冗长。建设性的批评表示赞赏。
当我在 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 定义一个 class 时,这些函数似乎不是默认生成的(有没有办法指定这个?),所以我试图添加这个我自己的功能,因为我希望 a) 这种语法在结构和 classes 之间保持一致,b) 有一个 (typep obj 'classname)
的缩写,我需要经常写它并且在视觉上很吵,
和 c) 作为编程练习,因为我对 Lisp 还是比较陌生。
我可以编写一个宏来定义给定一个谓词函数的 class:
(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创建的symbol intern为|'BOOK-P|
?在 On Lisp(同上)中,Graham 说:"Any string can be the print-name of a symbol, even a string containing lowercase letters or macro characters like parentheses. When a symbol's name contains such oddities, it is printed within vertical bars." 在这种情况下不存在这样的奇怪之处,是吗?我是否正确地认为符号的 "print-name" 是打印符号时标准输出上实际显示的内容,并且在这种奇怪的情况下,与符号本身的形式不同?
能够编写像 gen-predicate
这样的函数定义宏——其定义的函数是根据传递给宏的参数命名的——在我看来就像 Lisp 黑客可能已经做了很多年了。用户 Kaz 在这里 (Merging symbols in common lisp) 说符号的 "mashing-up" 通常可以避免,但这会破坏这个宏的目的。
最后,假设我可以让 gen-predicate
以我想要的方式工作,那么什么是确保每个新的 class 都按定义调用它的最佳方法?与 initialize-instance
可以自定义以在 class 的 实例化 上执行某些操作的方式非常相似,是否存在由 def[=55= 调用的通用函数] 可以根据 class?
谢谢。
这是一个常见的问题:传递给宏的是什么?
像这样比较调用:
(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
是一个宏,第一个参数是函数名。这是一个类似的问题...
第二部分问题
我真的不知道有什么好的答案。我不记得在 class 定义之后 运行 编码(例如定义函数)的任何简单方法。
也许可以使用 MOP,但那太丑了。
写一个自定义宏
DEFINE-CLASS
做你想做的事:扩展成DEFCLASS
和DEFUN
.遍历包中的所有符号,找到classes并定义相应的谓词
要解决问题的第二部分,classes 本身就是对象,多亏了 MOP,因此 可能 可以编写一个 :after 方法在专门针对 STANDARD-CLASS 的初始化实例上。但是你应该检查MOP,看看是否允许定义这样的方法。
如果可能的话,是的,您可以 运行 代码响应 class 的创建;但是,由于您直到 运行 时才知道正在创建的 class 的名称,因此您无法在源代码中以文本方式拼写它,因此您无法使用您的宏(除非您使用 eval)。你宁愿使用像
这样的东西(let ((classname (class-name class)))
(compile (generate-my-predicate-symbol classname)
(lambda (x) (typep x classname))))
我认为 Rainer 关于编写自己的 DEFINE-CLASS 宏的建议是可行的方法,我的意思是,如果没有任何其他考虑,经验丰富的 Lisper 很可能会这样做玩。但我并不是一个经验丰富的 Lisper,所以我可能是错的 ;)