A Riddle Wrapped in a Mystery

中身はエニグマ・Emacs

Emacsの汎変数

Emacs 24.3 で導入された汎変数を使えば、ソースコードの可読性が高まる可能 性がある。

setf の例

汎変数の代入マクロ setf を使えば、 car だけで setcar 相当が記述できる。

(setq a (cons 3 5))
(setf (car a) 10)
a
→ (10 . 5)

setcar/setcdr という関数を別に覚えるよりは、学習コストは低い。同様に、

(setq a (list 1 10 100 1000 10000))
(elt a 3)
→ 1000
(setf (elt a 3) 300)
a
→ (1 10 100 300 10000)

となる。

setf に対応している関数

24.3 で、setf に対応している関数は以下の通り。setf を使えば右の関数は 不要になる。覚える関数が減れば、プログラム作成・保守も楽になる。

汎変数の対象 対応する設定関数
   
aref aset
car setcar
cdr setcdr
caar setcar/car
cadr setcar/cdr
cdar setcdr/car
cddr setcdr/cdr
elt setcar/nthcdr
get put
gethash puthash
nth nthcdr/setcar
nthcdr nthcdr/setcdr
default-value set-default
frame-parameter set-frame-parameter
terminal-parameter set-terminal-parameter
keymap-parent set-keymap-parent
match-data set-match-data
overlay-get overlay-put
overlay-start move-overlay
overlay-end move-overlay
process-buffer set-process-buffer
process-filter set-process-filter
process-sentinel set-process-sentinel
process-get process-put
window-buffer set-window-buffer
window-display-table set-window-display-table
window-dedicated-p set-window-dedicated-p
window-hscroll set-window-hscroll
window-parameter set-window-parameter
window-point set-window-point
window-start set-window-start
progn  
let, let*  
cond  
if  
cons  
logand  

汎変数を使うと、構造の構築子とアクセス関数のペアだけで、構造に式を代入 できる。たとえば hash の場合、これまではmake-hash-table, gethash の他に puthash が必要だったが、setf を使えばputhash は不要になる。

(puthash key val table) ≅ (setf (gethash key table) val)

汎変数の作り方

gv.el の最後には、コメントアウトされている alist-get 関数の実装例があり、 自分の汎変数を作る際の参考になる。

 1: (defun alist-get (key alist)
 2:   "Get the value associated to KEY in ALIST."
 3:   (declare
 4:    (gv-expander
 5:     (lambda (do)
 6:       (macroexp-let2 macroexp-copyable-p k key
 7:         (gv-letplace (getter setter) alist
 8:           (macroexp-let2 nil p `(assoc ,k ,getter)
 9:             (funcall do `(cdr ,p)
10:                      (lambda (v)
11:                        `(if ,p (setcdr ,p ,v)
12:                           ,(funcall setter
13:                                     `(cons (cons ,k ,v) ,getter)))))))))))
14:           (cdr (assoc key alist)))

上記は一見複雑だが、重要なのは以下の3点である。

  • 汎変数にしたい関数は、関数定義内で (declare (gv-expander (lambda (do) …))) を宣言するか、または関数定義外で (put 'func 'gv-expander (lambda (do arg1 arg2 …) …)) で汎変数関数を設定する。 [fn:: ~declare~ 経由で汎変数関数を設定する場合、 ~do~ 以外の引数の追 加は ~gv–defun-declaration~ が自動的に行なってくれる。]
  • 汎変数関数は、関数 do と 元関数の引数を引数にとる。そして汎変数関 数の中では do の引数の形で、 setter 処理を行う式を出力するコード と getter 処理を行う式を出力するコードを記述する。上記の例では getter`(cdr ,p) で、 setter(lambda (v) `(if (….getter))) となる。その際、 setter の中に getter を記述して も構わない。
  • (gv-letplace (setter getter) place body) は、上記のように書かれた汎 変数関数に対して、 place リストの最初の要素の関数に対応する汎変数関 数の getter 関数と setter 関数を取り出して、それぞれをシンボル setter getter に束縛する。

この方式は、かなり洗練されていて面白い。Emacsの汎変数がこのような形で設 計されているのは以下の理由による。

  • setter プログラムと getter プログラムを同一関数内で記述することで、 構文スコープ下で両機能で変数を共有でき(クロージャ)、互いにネストし たり副作用を与えられる自由度の高いコードがかける。
  • マクロで記述しているので高速化ができる。特にEmacsの汎変数の根幹であ る gv-get マクロは事実上コンパイラになっている。

この do にどのような関数を入れるのかは、アプリケーションによって異な る。たとえば setf は、 do 関数として、 setter/getter 両引数のうち setter のみを呼び出して汎変数に引数の式を入れる。また gv-letplace は、 do として引数の settergetter をそのまま (setter getter) に紐づける lambda 関数を使っている。

上記の例である alist-get は、cl.el の cl-assoc と同じ機能を持つ関 数である。 gv-expander で設定される関数は、関数 do, 位置 key, 構 造 alist を引数とする。

(macroexp-let2 macroexp-copyable-p k key …) により、引数 key がコピーされて、 k に代入される。

(gv-letplace (setter getter) alist …) は、 alist の最初の要素( 当然 alist-get となる)に対応する汎変数関数(すなわち自分自身) に対して、その値を操作する2関数 gettersetter (すなわち自分自 身の gettersetter プログラム)を取り出す。 getter は現在の key での alist-get の値を返し、 setter は引数の値を alist に入 れる。

(macroexp-let2 nil p `(assoc ,k ,getter) … ) で、 palistk 対応値(Consセル)が入る。

setf が実行されると、 setf 内の do 関数は2つめのの引数の setter を使って値を代入し、結果として得られる式を評価する。

以下は実行例。

(setq a '(("a" . "b") ("c" . "d")))
(alist-get "a" a) → "b"
(setf (alist-get "a" a) "q")
(alist-get "a" a) → "b""q"

Emacsの汎変数を記述する gv.el は、コンパイラやモナドに通じる興味深いコー ドとなっている。

Footnotes:

1

declare 経由で汎変数関数を設定する場合、 do 以外の引数の追 加は gv–defun-declaration が自動的に行なってくれる。