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
として引数の setter
と getter
をそのまま (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関数 getter
と setter
(すなわち自分自
身の getter
と setter
プログラム)を取り出す。 getter
は現在の
key
での alist-get
の値を返し、 setter
は引数の値を alist
に入
れる。
(macroexp-let2 nil p `(assoc ,k ,getter) … )
で、 p
に alist
の
k
対応値(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:
declare
経由で汎変数関数を設定する場合、 do
以外の引数の追
加は gv–defun-declaration
が自動的に行なってくれる。