【发布时间】:2015-08-05 14:05:19
【问题描述】:
*注意:尽管很长时间都经常光顾 StackOverflow,但这是我自己发布的第一个问题。抱歉,如果它有点冗长。建设性的批评表示赞赏。
当我在 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 调用的通用函数可以根据定义 执行操作一个班级?
谢谢。
【问题讨论】:
标签: macros common-lisp symbols sbcl