使用宏来定义数据结构

宏的一个常见用途是为数据结构创建模板,这些模板遵循通用规则但可能包含不同的字段。通过编写宏,你可以允许指定数据结构的详细配置,而无需重复样板代码,也不允许在内存中使用效率较低的结构(如散列)来简化编程。

例如,假设我们希望定义一些具有一系列不同属性的类,每个属性都有一个 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)),插槽 barbazqux 带有调用 changed 方法的 setter(其中 getter 由宏的第一部分 (bar :reader bar) 和调用的 notifier 宏的 setter 定义。

除了允许我们快速定义多个以这种方式运行的类,具有大量属性而不重复之外,我们还有代码重用的常见好处:如果我们后来决定更改通知方法的工作方式,我们可以简单地更改宏,每个使用它的类的结构都会改变。