公共Lisp:控制宏展开时间



我使用的是普通的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)))

:)

相关内容

  • 没有找到相关文章

最新更新