使用巨集來定義資料結構
巨集的一個常見用途是為資料結構建立模板,這些模板遵循通用規則但可能包含不同的欄位。通過編寫巨集,你可以允許指定資料結構的詳細配置,而無需重複樣板程式碼,也不允許在記憶體中使用效率較低的結構(如雜湊)來簡化程式設計。
例如,假設我們希望定義一些具有一系列不同屬性的類,每個屬性都有一個 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 定義。
除了允許我們快速定義多個以這種方式執行的類,具有大量屬性而不重複之外,我們還有程式碼重用的常見好處:如果我們後來決定更改通知方法的工作方式,我們可以簡單地更改巨集,每個使用它的類的結構都會改變。