複数の変数を一個のリストにまとめて、デバッグのために表示したいとする。
(defun sut (arg)
"テスト対象"
(let ((a 1) (b 10) (c 100))
(message "sut: %s" (list a b c arg)))
t)
メッセージバッファにはこのように表示されるだろう。
sut: (1 10 100 hogehoge)
だが、願わくばどの値がどの変数なのかをいっしょに表示したい。なのでこうしてみる。
- (message "sut: %s" (list a b c arg)))
+ (message "sut: %s" (list :a a :b b :c c :arg arg)))
するとメッセージバッファの表示ではこのようになり、どの変数がどの値なのかわかりやすくなる。
sut: (:a 1 :b 10 :c 100 :arg hogehoge)
このような (キー1 値1 キー2 値2 …)
のように平坦なリストの中でキーと値が互い違いに現れるような構造のリストは属性リスト(plist)と呼ばれるものであることは「Emacsを書く前のLisp」という記事にも書いた。
表示するための情報量としては十分なものだろう。しかし問題がある。リストの構築がめんどくさい。
そういうときはマクロを使う。
(defmacro compact (&rest variables)
"Compact variables into plist."
(let ((var (cl-gensym "compact")))
`(cl-loop for ,var in (quote ,variables)
nconc (list (intern (format ":%s" ,var))
(symbol-value ,var)))))
compact
というマクロ名はPHPの同名の関数から拝借した。
Lispには詳しくないのでCommon Lispなどにも同様の機能が既にある可能性もある。
Emacs Lispのマクロで使える変数はマクロ内に閉じていない(別の言い方をすると健全なマクロではない)ので、単にsymbol-values
を使うとマクロ外の変数も平然と参照できる。
- (message "sut: %s" (list :a a :b b :c c :arg arg)))
+ (message "sut: %s" (compact a b c arg)))
見ためがとてもすっきりした。
世の中には「俺はプロパティリストは好かん、連想リストしか勝たん」というやつもおろう。仕方がない。
(defmacro compact-assoc (&rest variables)
"Compact variables into plist."
(let ((var (cl-gensym "var")))
`(cl-loop for ,var in (quote ,variables)
collect (cons ,var (symbol-value ,var)))))
sut: ((a . 1) (b . 10) (c . 100) (arg . hogehoge))
俺はどっちでもいいけど。
追記
@ROCKTAKEY 先生から
完全にその通り。
(defmacro compact-list (&rest variables)
"Compact variables into plist."
(cons 'list
(cl-loop for var in variables
nconc (list (intern (format ":%s" var)) var))))
`
も ,
も出てこないし、異様にすっきりしましたね… gensym
とかしなくても変数名汚染の心配がないのがおいしい。
追記2
マクロで symbol-value
をとろうと思ったら何か怪しいという嗅覚が自分には身についていなかった。
このあたりは曖昧だったので、さらなる理解は筆者(@tadsan)への課題とします。