デザイン(設計)パターンという程のことはない、Emacs Lisp のパターンを思いつくままに書きます。心は、
- 高階関数を書こう
- マクロを書こう
です。
mapcar
mapcar は、引数に関数をとる高階関数のよい例です。リストを取り、それぞれの値を加工して、新しいリストを返すパターンのときは、mapcar を使いましょう。
(mapcar '1+ '(1 2 3 4)) ;; => (2 3 4 5)
mapcar には、自分のさせたい仕事を実装した関数を渡しましょう。
(defun f(x) (1+ (* x 2))) (mapcar 'f '(1 2 3 4)) ;; => (3 5 7 9)
mapcar を連結しましょう。(オブジェクト指向でのメッセージの連結に似ていますね。)
(mapcar '1+ (mapcar (lambda(x) (* x 2)) '(1 2 3 4))) ;; => (3 5 7 9)
データを走査する関数
「なぜ関数プログラミングは重要か」より。
あるデータに対し、そのデータを走査する部分(高階関数)と仕事をする部分は分離しましょう。
リストの要素の和を計算する関数は以下のようになります。
(defun sum (lst) (if (null lst) 0 (+ (car lst) (sum (cdr lst))))) (sum '(1 2 3 4)) ;; => 10
リストの要素の積を計算する関数は以下のようになります。
(defun product (lst) (if (null lst) 1 (* (car lst) (product (cdr lst))))) (product '(1 2 3 4)) ;; => 24
これらをじっと眺め、取り扱っているリストというデータを走査する高階関数 reduce-list を以下のように書きます。
(defun reduce-list(func init lst) (if (null lst) init (funcall func (car lst) (reduce-list func init (cdr lst)))))
reduce-list を使うと、sum や product は以下のように定義できます。
(defun sum(lst) (reduce-list '+ 0 lst)) (sum '(1 2 3 4)) ;; => 10 (defun product(lst) (reduce-list '* 1 lst)) (product '(1 2 3 4)) ;; => 24
ここでは、reduce-list にプリミティブな関数が渡されていますが、一般的には自分のやりたい仕事を実装した関数になります。
こうしておけば、データを走査する関数と仕事をする関数は、独立に変更できます。例として、reduce-list の実装を変えてみましょう。
(defun reduce-list(func init lst) (let ((ret init)) (while lst (setq ret (funcall func ret (car lst))) (setq lst (cdr lst))) ret)) (defun sum(lst) (reduce-list '+ 0 lst)) (sum '(1 2 3 4)) ;; => 10
データ構造がリストから配列に変わってもいいでしょう。
(defun reduce-array(func init arr) (let ((ret init) (i 0) (len (length arr))) (while (< i len) (setq ret (funcall func ret (aref arr i))) (setq i (1+ i))) ret)) (defun sum(arr) (reduce-array '+ 0 arr)) (sum [1 2 3 4]) ;; => 10
マクロ
「On Lisp」より。
Lisp のデータとプログラムには区別がありません。どちらの括弧のお化けです。S 式と呼ばれたりしますが、古の M 式と区別する必要がなければ、単に「式」でいいでしょう。
- 式は値を生成します
- マクロは式を生成します
コード中に繰り返し現れる制御構造があれば、マクロにしてしまいましょう。先ほどの reduce-list を思い出して下さい。
(defun reduce-list(func init lst) (let ((ret init)) (while lst (setq ret (funcall func ret (car lst))) (setq lst (cdr lst))) ret))
これは、マクロ dolist を使って抽象化できます。
(defun reduce-list(func init lst) (let ((ret init)) (dolist (a lst ret) (setq ret (funcall func ret a)))))
dolist の定義は、subr.el を読んで下さい。
マクロを展開すると、以下のようになっていることが分ります。
(pp (macroexpand-all ' (defun reduce-list(func init lst) (let ((ret init)) (dolist (a lst ret) (setq ret (funcall func ret a))))) )) ;; => (defun reduce-list (func init lst) (let ((ret init)) (cl-block-wrapper (catch '--cl-block-nil-- (let ((--cl-dolist-temp-- lst) a) (while --cl-dolist-temp-- (setq a (car --cl-dolist-temp--)) (setq ret (funcall func ret a)) (setq --cl-dolist-temp-- (cdr --cl-dolist-temp--))) (setq a nil) ret)))))
Graham さんは、マクロを書けば、Lisp を自分の理想とする言語に近づけられると主張しています。