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

时间:2015-05-23 17:14:49

标签: macros common-lisp symbols sbcl

*注意:尽管已经经常使用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

但是,在使用defclass定义类时,默认情况下这些函数似乎没有生成(有没有办法指定这个?),所以我试图自己添加这个功能,因为我喜欢a)for这个语法在结构和类之间是一致的,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中的“symb”函数(第58页)。当它在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调用的泛型函数一个班级?

谢谢。

2 个答案:

答案 0 :(得分:5)

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

比较这样的来电:

(symb 'book '-p)

(symb ''book '-p)

您的宏观形式是:

(gen-predicate 'book)

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

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

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

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

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

(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

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

答案 1 :(得分:1)

为了解决问题的第二部分,由于MOP,类本身就是对象,因此可能可以编写一个:在初始化实例上的方法专门用于STANDARD-CLASS。但是你应该检查MOP以确定是否允许定义这样的方法。

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

这样的东西
(let ((classname (class-name class)))
  (compile (generate-my-predicate-symbol classname)
    (lambda (x) (typep x classname))))

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