在做CLOS类时,我遇到了多次相同的模式:
(defclass class-name ()
((field-1
:initarg field-1
:initform some-value
:accessor field-1)
(field-2
:initarg field-2
:initform another-value
:accessor field-2)
(...)
(field-n
:initarg field-n
:initform n-value
:accessor field-n)))
(这是否是良好的设计,我将随着时间的推移而学习)
我尝试使用宏来解决这个问题,以便我可以调用:
(defclass-with-accessors 'class-name
(('field-1 some-value)
('field-2 another-value)
(...)
('field-n n-value)))
忽略卫生问题,我的第一个尝试是拆分成两个宏:一个用于创建每个字段,另一个用于创建类本身。
创建访问器字段的宏似乎是正确的:
(defmacro make-accessor-field (name form)
`(,name
:initarg ,(make-keyword name)
:initform ,form
:accessor ,name))
但是我没有正确理解主要的宏。我的第一次尝试是:
(defmacro defclass-with-accessors (name body)
`(defclass ,name () \(
,(loop for my-slot in body collect
(make-accessor-field (car my-slot) (cadr my-slot)))))
但是这是无效的,SBCL在defmacro评估时会给我以下错误:
; in: DEFMACRO DEFCLASS-WITH-ACCESSORS
; (MAKE-ACCESSOR-FIELD (CAR MY-SLOT) (CADR MY-SLOT))
;
; caught ERROR:
; during macroexpansion of (MAKE-ACCESSOR-FIELD (CAR MY-SLOT) (CADR MY-SLOT)).
; Use *BREAK-ON-SIGNALS* to intercept.
;
; The value (CAR MY-SLOT)
; is not of type
; (OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING SYMBOL CHARACTER).
;
; compilation unit finished
; caught 1 ERROR condition
STYLE-WARNING:
redefining COMMON-LISP-USER::DEFCLASS-WITH-ACCESSORS in DEFMACRO
到底发生了什么?当slot甚至没有被定义时,编译器如何确定(car slot)的类型?我该如何继续正确定义这个宏?