使用巨集來定義資料結構

巨集的一個常見用途是為資料結構建立模板,這些模板遵循通用規則但可能包含不同的欄位。通過編寫巨集,你可以允許指定資料結構的詳細配置,而無需重複樣板程式碼,也不允許在記憶體中使用效率較低的結構(如雜湊)來簡化程式設計。

例如,假設我們希望定義一些具有一系列不同屬性的類,每個屬性都有一個 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 定義。

除了允許我們快速定義多個以這種方式執行的類,具有大量屬性而不重複之外,我們還有程式碼重用的常見好處:如果我們後來決定更改通知方法的工作方式,我們可以簡單地更改巨集,每個使用它的類的結構都會改變。