在宏上使用循环在 Common Lisp 中生成 类 个槽
Using loop on a macro to generate classes slots in Common Lisp
在 CLOS 上制作 classes 时,我多次遇到相同的模式:
(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)))
我的第一个解决方案(暂时忽略卫生)是分成两个宏:一个用于制作每个字段,另一个用于制作 class 本身。
制作访问器字段的宏似乎是正确的:
(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
到底发生了什么?甚至没有定义插槽时,编译器如何判断(汽车插槽)的类型?我怎样才能继续正确定义这个宏?
基本错误
这个宏是错误的,因为它不应该是一个宏:
(defmacro make-accessor-field (name form)
`(,name
:initarg ,(make-keyword name)
:initform ,form
:accessor ,name))
宏窗体应扩展为代码。此宏将表单扩展为用于插槽描述的列表。插槽描述不是代码,而是 defclass
插槽列表的一部分。因此你不能使用这样的宏,因为返回的值应该是代码,而不是插槽描述列表。
也通常不会在宏名称中使用 MAKE-
。这更像是一个约定。 MAKE-SOMETHING
应该是一个函数。每当你 make 东西时,就会在运行时创建一些东西,因此它应该是一个函数。有时人们还想将 make 应用于一系列事物,然后再一次,首选函数。
这也是错误的,因为有一个带括号的符号作为它的名字:
(defmacro defclass-with-accessors (name body)
`(defclass ,name () \( ; <- what is this?
,(loop for my-slot in body collect
(make-accessor-field (car my-slot) (cadr my-slot)))))
这段代码也不是一个好主意,因为引号没有用:
(defclass-with-accessors 'class-name
(('field-1 some-value)
('field-2 another-value)
(...)
('field-n n-value)))
如果您查看 defclass
,它不需要引号。因此,在您的 defclass
变体中,也不应该有引号。
让我们尝试改进它
示例 form 来自一些虚构的代码库:
(defclass-with-accessors foo
((bar 10)
(baz (sin pi)))
MAKE-ACCESSOR-FIELD
现在是一个函数:
(defun make-accessor-field (name form)
`(,name
:initarg ,(intern (symbol-name name) "KEYWORD")
:initform ,form
:accessor ,name))
新的DEFCLASS-WITH-ACCESSORS
:
(defmacro defclass-with-accessors (name slot-descriptions)
`(defclass ,name ()
,(loop for (slot-name form) in slot-descriptions
collect (make-accessor-field slot-name form))))
让我们检查展开:
macroexpand-1
在顶层展开一次表单,pprint
以某种自动格式化的方式打印 s 表达式:
CL-USER 12 > (pprint (macroexpand-1 '(defclass-with-accessors foo
((bar 10)
(baz (sin pi))))))
(DEFCLASS FOO
NIL
((BAR :INITARG :BAR :INITFORM 10 :ACCESSOR BAR)
(BAZ :INITARG :BAZ :INITFORM (SIN PI) :ACCESSOR BAZ)))
看起来不错。
在 CLOS 上制作 classes 时,我多次遇到相同的模式:
(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)))
我的第一个解决方案(暂时忽略卫生)是分成两个宏:一个用于制作每个字段,另一个用于制作 class 本身。
制作访问器字段的宏似乎是正确的:
(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
到底发生了什么?甚至没有定义插槽时,编译器如何判断(汽车插槽)的类型?我怎样才能继续正确定义这个宏?
基本错误
这个宏是错误的,因为它不应该是一个宏:
(defmacro make-accessor-field (name form)
`(,name
:initarg ,(make-keyword name)
:initform ,form
:accessor ,name))
宏窗体应扩展为代码。此宏将表单扩展为用于插槽描述的列表。插槽描述不是代码,而是 defclass
插槽列表的一部分。因此你不能使用这样的宏,因为返回的值应该是代码,而不是插槽描述列表。
也通常不会在宏名称中使用 MAKE-
。这更像是一个约定。 MAKE-SOMETHING
应该是一个函数。每当你 make 东西时,就会在运行时创建一些东西,因此它应该是一个函数。有时人们还想将 make 应用于一系列事物,然后再一次,首选函数。
这也是错误的,因为有一个带括号的符号作为它的名字:
(defmacro defclass-with-accessors (name body)
`(defclass ,name () \( ; <- what is this?
,(loop for my-slot in body collect
(make-accessor-field (car my-slot) (cadr my-slot)))))
这段代码也不是一个好主意,因为引号没有用:
(defclass-with-accessors 'class-name
(('field-1 some-value)
('field-2 another-value)
(...)
('field-n n-value)))
如果您查看 defclass
,它不需要引号。因此,在您的 defclass
变体中,也不应该有引号。
让我们尝试改进它
示例 form 来自一些虚构的代码库:
(defclass-with-accessors foo
((bar 10)
(baz (sin pi)))
MAKE-ACCESSOR-FIELD
现在是一个函数:
(defun make-accessor-field (name form)
`(,name
:initarg ,(intern (symbol-name name) "KEYWORD")
:initform ,form
:accessor ,name))
新的DEFCLASS-WITH-ACCESSORS
:
(defmacro defclass-with-accessors (name slot-descriptions)
`(defclass ,name ()
,(loop for (slot-name form) in slot-descriptions
collect (make-accessor-field slot-name form))))
让我们检查展开:
macroexpand-1
在顶层展开一次表单,pprint
以某种自动格式化的方式打印 s 表达式:
CL-USER 12 > (pprint (macroexpand-1 '(defclass-with-accessors foo
((bar 10)
(baz (sin pi))))))
(DEFCLASS FOO
NIL
((BAR :INITARG :BAR :INITFORM 10 :ACCESSOR BAR)
(BAZ :INITARG :BAZ :INITFORM (SIN PI) :ACCESSOR BAZ)))
看起来不错。