はじめに
最近、実用Common Lispを読んでます。なるほどと思う情報が多いので、非常に勉強になってます。この中でsubstとsublisについて書かれていたので、忘れないようにメモしておこうと思います。
substとsublisとは
この2つの関数はツリーに対する処理を行う関数です。ツリーとは入れ子になったリストです。
substとsublisは、ツリー内の要素の置換を行います。substは単一の要素、sublisは複数の要素の置換を一気に行うことができます。下に例を示します。
(subst 'new 'old '(old (a b) ((old))))
;=> (NEW (A B) ((NEW)))
(sublis '((old . new) (b . a)) '(old (a b) ((old))))
;=> (NEW (A A) ((NEW)))
sublisは置換前と置換後の連想リストを引数として渡しています。
実用Common Lispではsubstとsublisの、newとoldの順番の違いについて触れていました。確かに渡す引数の順番に一貫性がないと、混乱してしまいます。
一貫性のあるようにするには、substの引数の順番を変更したマクロを定義する方法と、sublisの引数の連想リストを、キーと値を入れ替える関数を挟んで渡すように展開するマクロを定義する方法があると思いました。
実装
下に前者のマクロの定義を示します。
(defmacro my-subst (old new tree &rest rest)
`(subst ,new ,old ,tree ,@rest))
(my-subst 'old 'new '(old (a b) ((old))))
;=> (NEW (A B) ((NEW)))
次に後者のマクロの定義を示します。連想リストのキーと値を入れ替える関数を、ループと再帰で書いたので両方とも書いておきます。
;;; ループ版
(defun swap-key-value (alist)
(let ((result nil))
(dolist (elem alist)
(push (cons (cdr elem) (car elem)) result))
(nreverse result)))
;;; 再帰版
(defun swap-key-value (alist)
(swap-key-value-rec alist nil))
(defun swap-key-value-rec (alist acc)
(if (null alist)
(nreverse acc)
(swap-key-value-rec
(rest alist)
(push (cons (cdr (first alist)) (car (first alist))) acc))))
(swap-key-value '((a . key) (b . value) (c . subst) (d . sublis)))
;=> ((KEY . A) (VALUE . B) (SUBST . C) (SUBLIS . D))
(defmacro my-sublis (alist tree &rest rest)
`(sublis (swap-key-value ,alist) ,tree ,@rest))
(my-sublis '((new . old) (a . b)) '(old (a b) ((old))))
;=> (NEW (A A) ((NEW)))
おわりに
Common Lispの関数は、引数の順番に一貫性がなかったり、関数の名前が中途半端に短くしている(個人的な意見として例えばclrhash)があったりします。この時に我慢して標準の関数を使うのか、新たに自分でマクロを定義するのか迷うところです。どちらの方法にも長所と短所があるのですが、標準で用意されているもの関数はそれを使う方がいいのかもしれません。
ですがLispという自由度が非常に大きい言語を使うと自分の好みに合うように変更したい気持ちもあります。他の人のソースコードを読む負担が激増してしまいそうですが、自分が気持よくプログラムを書くこともそれと同じくらい重要だと思っています。