我正在使用常见的lisp,发现自己输入了以下表单的插槽定义:
(name :initarg :name :accessor name)
所以我想编造一个宏来加速这个。我想出了以下内容:
(defmacro quickslot (name)
`(,name :initarg ,(intern (string-upcase name) "KEYWORD") :accessor ,name))
肮脏的黑客,毫无疑问,但功能性。或者我想。当我试图运行我的代码时,我遇到了一个障碍:因为defclass是一个宏,所以参数传递给它没有评估。这意味着,而不是看到
(x :initarg :x :accessor x)
它看到了
(quickslot x)
当然,这表示错误。
在我看来,答案是以某种方式控制宏扩展的顺序,以确保在defclass之前扩展quickslot。这让我想到了一个问题:如何实现这个目标?或者,如果你对我最初的难题有不同的解决方案,那也不会不受重视。
答案 0 :(得分:4)
这不值得一个宏。宏通常将文字Lisp源代码作为输入。
相反,你可以使用一个函数。来自Practical Common Lisp, Ch.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)))
答案 1 :(得分:1)
不,你不能这样做。你可以在defclass
周围编写一个宏(尽管你的快速时隙有一些特殊的语法)。
答案 2 :(得分:1)
您可以完全不同地处理问题,并提出一个读取器宏,指示读者在其后面的代码上调用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)))
:)