あどけない話

インターネットに関する技術的な話など

Emacs Lisp のパターン

デザイン(設計)パターンという程のことはない、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 を自分の理想とする言語に近づけられると主張しています。