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

And*_*age 6 macros symbols sbcl common-lisp

*注意:尽管已经经常使用StackOverflow,但这是我自己发布的第一个问题.道歉,如果它有点冗长.建设性批评赞赏.

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

(defstruct book
  title
  author)

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

但是,在使用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)
Run Code Online (Sandbox Code Playgroud)

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

(symb 'book '-p)
=> BOOK-P
Run Code Online (Sandbox Code Playgroud)

到目前为止,我的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
Run Code Online (Sandbox Code Playgroud)

似乎创建的符号(symb 'book '-p)实际上是|'BOOK-P|由实现(SBCL)考虑的,而不是BOOK-P.果然,这现在有效:

(let ((huck-finn (make-instance 'book)))
  (|'BOOK-P| huck-finn))
=> True
Run Code Online (Sandbox Code Playgroud)

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

为了能够编写函数定义宏gen-predicate- 比如定义的函数是根据传递给宏的参数命名的 - 在我看来似乎是Lisp黑客可能已经做了多年的事情.用户Kaz在这里说(在常见的lisp中合并符号)经常可以避免符号的"混搭",但这会破坏这个宏的目的.

最后,假设我可以按照自己的gen-predicate方式工作,那么确保在定义每个新类时调用它的最佳方法是什么?与在实例化initialize-instance时可以自定义执行某些操作的方式相同,是否存在可以在定义类时执行操作的defclass调用的泛型函数?

谢谢.

Rai*_*wig 5

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

比较这样的电话:

(symb 'book '-p)
Run Code Online (Sandbox Code Playgroud)

(symb ''book '-p)
Run Code Online (Sandbox Code Playgroud)

你的宏形式是这样的:

(gen-predicate 'book)
Run Code Online (Sandbox Code Playgroud)

GEN-PREDICATE是一个宏.classname是此宏的参数.

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

实际上它是后者,因为你写了(gen-predicate 'book).请记住:宏查看源代码并将参数源传递给宏函数 - 而不是值.争论是'book.这样就过去了.(QUOTE BOOK)是相同的,只是印刷不同.所以它是一个两元素列表.第一个元素是符号QUOTE,第二个元素是符号BOOK.

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

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

(gen-predicate book)
Run Code Online (Sandbox Code Playgroud)

或者,您也可以更改宏:

(symb classname '-p)
Run Code Online (Sandbox Code Playgroud)

将会:

(symbol (if (and (consp classname)
                 (eq (first classname) 'quote))
           (second classname)
           classname))
Run Code Online (Sandbox Code Playgroud)

相比

我们写

(defun foo () 'bar)
Run Code Online (Sandbox Code Playgroud)

并不是

(defun 'foo () 'bar)    ; note the quoted FOO
Run Code Online (Sandbox Code Playgroud)

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

问题的第二部分

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

  • 也许使用MOP,但那很难看.

  • 编写一个自定义宏DEFINE-CLASS,它可以执行您想要的操作:扩展为DEFCLASSDEFUN.

  • 迭代包中的所有符号,找到类并定义相应的谓词