使用宏来定义数据结构
宏的一个常见用途是为数据结构创建模板,这些模板遵循通用规则但可能包含不同的字段。通过编写宏,你可以允许指定数据结构的详细配置,而无需重复样板代码,也不允许在内存中使用效率较低的结构(如散列)来简化编程。
例如,假设我们希望定义一些具有一系列不同属性的类,每个属性都有一个 getter 和 setter。此外,对于某些(但不是全部)这些属性,我们希望 setter 在对象上调用一个方法,通知它该属性已被更改。虽然 Common LISP 已经有了写入 getter 和 setter 的简写,但是以这种方式编写标准的自定义 setter 通常需要复制在每个 setter 中调用通知方法的代码,如果涉及大量属性,这可能会很痛苦。但是,通过定义宏,它变得更容易:
(defmacro notifier (class slot)
"Defines a setf method in (class) for (slot) which calls the object's changed method."
`(defmethod (setf ,slot) (val (item ,class))
(setf (slot-value item ',slot) val)
(changed item ',slot)))
(defmacro notifiers (class slots)
"Defines setf methods in (class) for all of (slots) which call the object's changed method."
`(progn
,@(loop for s in slots collecting `(notifier ,class ,s))))
(defmacro defclass-notifier-slots (class nslots slots)
"Defines a class with (nslots) giving a list of slots created with notifiers, and (slots) giving a list of slots created with regular accessors."
`(progn
(defclass ,class ()
( ,@(loop for s in nslots collecting `(,s :reader ,s))
,@(loop for s in slots collecting `(,s :accessor ,s))))
(notifiers ,class ,nslots)))
我们现在可以编写 (defclass-notifier-slots foo (bar baz qux) (waldo))
并立即定义一个类 foo
,它有一个常规插槽 waldo
(由宏的第二部分创建,带有规范 (waldo :accessor waldo)
),插槽 bar
,baz
和 qux
带有调用 changed
方法的 setter(其中 getter 由宏的第一部分 (bar :reader bar)
和调用的 notifier
宏的 setter 定义。
除了允许我们快速定义多个以这种方式运行的类,具有大量属性而不重复之外,我们还有代码重用的常见好处:如果我们后来决定更改通知方法的工作方式,我们可以简单地更改宏,每个使用它的类的结构都会改变。