我使用的是普通的lisp语言,我发现自己键入了以下形式的槽定义:
(name :initarg :name :accessor name)
所以我想编造一个宏来加速这个。我想到了以下内容:
(defmacro quickslot (name)
`(,name :initarg ,(intern (string-upcase name) "KEYWORD") :accessor ,name))
毫无疑问,这是一个肮脏的hack,但是很有用。至少我是这么想的。当我试图运行我的代码时,我遇到了一个问题:由于defclass是一个宏,传递给它的参数是未求值的。这意味着,与看到
不同,(x :initarg :x :accessor x)
,
(quickslot x)
当然,这表示错误。
在我看来,答案是以某种方式控制宏展开的顺序,以确保在defclass之前展开quickslot。这就引出了我的问题:如何做到这一点?或者,如果你对我最初的难题有不同的解决方案,我也不会不感激的。
这并不值得使用宏。宏通常接受Lisp源代码作为输入。
你可以直接使用函数。选自实用通用Lisp,第24章:
(defun as-keyword (sym) (intern (string sym) :keyword))
(defun slot->defclass-slot (spec)
(let ((name (first spec)))
`(,name :initarg ,(as-keyword name) :accessor ,name)))
然后你可以这样做(同样改编自PCL):
(defmacro my-defclass (name slots)
`(defclass ,name ()
,(mapcar #'slot->defclass-slot slots)))
不行,你不能这么做。你可以在defclass
周围写一个宏(对于你的quickslots有一些特殊的语法)。
您可以完全不同地处理这个问题,并提出一个读取器宏,指示读取器在它后面的代码上调用macroexpand
,这将比在类中声明插槽的一个目的更通用。但是完整的解决方案有点复杂,因为您必须考虑到读者的许多特性和需求,然而,即使是像这样丑陋的东西也可以完成工作:
(defmacro quickslot (name)
`(,name :initarg ,(intern (string-upcase name) "KEYWORD") :accessor ,name))
(macroexpand '(defclass test-class ()
(#.(macroexpand '(quickslot some-slot)))))
那么你需要做的就是给#.(macroexpand ...)
…给你:
(set-macro-character
#{
#'(lambda (str char)
(declare (ignore char))
(let ((*readtable* (copy-readtable *readtable* nil))
(reading-p t))
(set-macro-character
#}
#'(lambda (stream char)
(declare (ignore char stream))
(setf reading-p nil)))
(loop for exp = (read str nil nil t)
while reading-p
collect (macroexpand exp)))))
(read-from-string "'(defclass test-class ()
{(quickslot some-slot)
(quickslot some-other-slot)})")
'(DEFCLASS TEST-CLASS NIL
((SOME-SLOT :INITARG :SOME-SLOT :ACCESSOR SOME-SLOT)
(SOME-OTHER-SLOT :INITARG :SOME-OTHER-SLOT :ACCESSOR
SOME-OTHER-SLOT)))
:)