Emacs
emacs-lisp

バッファローカル変数の挙動

More than 1 year has passed since last update.


バッファローカルとは


  • Emacs Lispのすべての変数は「バッファローカル」というフラグを持っている

  • このフラグはバッファごとに設定されている。たとえば、「変数xをバッファ*scratch*においてバッファローカルにする」といった操作ができる

  • 変数は、何も設定しなければ、どのバッファにおいても、バッファローカルではない


バッファローカル変数の性質


  • 変数には「バッファごとの値」と「デフォルト値」が設定できる

  • (現在のバッファにおいて)バッファローカルな変数の読み出し variable-name ・セット (setq variable-name 1) は、「バッファごとの値」に対して行われる

  • (現在のバッファにおいて)バッファローカルでない変数の読み出し・セットは、「デフォルト値」に対して行われる


local-variable-p

変数xが(現在のバッファにおいて)バッファローカルかどうかは、

(local-variable-p 'x)

で確認できる。


make-local-variablekill-local-variable

変数xを現在のバッファにおいてバッファローカルにするには、

(make-local-variable 'x)

を評価する。

変数xを現在のバッファにおいてバッファローカルでなくするには、

(kill-local-variable 'x)

を評価する。


default-valuesetq-default

(default-value 'x)

を評価すると、xがバッファローカルであるかどうかに関わらず、xのデフォルト値が読み出される。

(setq-default x 1)

を評価すると、xがバッファローカルであるかどうかに関わらず、xのデフォルト値が1にセットされる。


この例は、新たに作成したバッファ上で評価することを推奨する。また、評価にはlispxmp.elを用いることを推奨する。

(setq x 'default-value-1)

(make-local-variable 'x)
(setq x 'buffer-local-value)

(local-variable-p 'x) ; => t
x ; => buffer-local-value

(with-temp-buffer ; 一時バッファ上で評価
(local-variable-p 'x) ; => nil
x ; => default-value-1

(setq x 'default-value-2)
x ; => default-value-2
)

x ; => buffer-local-value
(default-value 'x) ; => default-value-2

(setq-default x 'default-value-3)
x ; => buffer-local-value
(default-value 'x) ; => default-value-3

(kill-local-variable 'x)
(local-variable-p 'x) ; => nil
x ; => default-value-3


バッファローカルとlet

Emacs Lispでは、letフォームなどによって、ローカルな変数を作ることができる。「バッファローカル」と「ローカル」は全く異なる概念である。


例1

この例は、新たに作成したバッファ上で評価することを推奨する。

現在のバッファ名をCurrent Bufferとする。

(setq x 'default-value-1)

(make-local-variable 'x)
(setq x 'buffer-local-value)

(local-variable-p 'x) ; => t
x ; => buffer-local-value

(let ((x 'buffer-local-value-in-let))
(local-variable-p 'x) ; => t
x ; => buffer-local-value-in-let

(with-temp-buffer
(local-variable-p 'x) ; => nil
x ; => default-value-1

(setq x 'default-value-2)
x ; => default-value-2
)

x ; => buffer-local-value-in-let
(default-value 'x) ; => default-value-2
)

x ; => buffer-local-value

with-temp-buffer内でのxの読み書きは、デフォルト値に対して行われる。したがって、'buffer-local-value-in-letを読み出すことはできない。また、with-temp-buffer内でxの値をセットしても、xCurrent Bufferにおいてバッファローカルであるため、値は変化しない。ただし、デフォルト値は変更されているので、(default-value 'x)によってその値を読み出すことができる。


例2

この例は、新たに作成したバッファ上で評価することを推奨する。

現在のバッファ名をCurrent Bufferとする。

(setq x 'default-value-1)

;; バッファ "Another Buffer" を作成し、 "Another Buffer" 上で評価
(with-current-buffer (get-buffer-create "Another Buffer")
(make-local-variable 'x)
(setq x 'buffer-local-value-1))

(local-variable-p 'x) ; => nil
x ; => default-value-1

(let ((x 'default-value-in-let))
(local-variable-p 'x) ; => nil
x ; => default-value-in-let

;; バッファ "Another Buffer" 上で評価
(with-current-buffer "Another Buffer"
(local-variable-p 'x) ; => t
x ; => buffer-local-value-1

(setq x 'buffer-local-value-2)
x ; => buffer-local-value-2
(default-value 'x) ; => default-value-in-let

(setq-default x 'default-value-2))

x ; => default-value-2
)

x ; => default-value-1

Another Bufferにおいてxはバッファローカルであるため、xの読み書きは、バッファごとの値に対して行われる。Current Bufferでセットした値は、(default-value 'x)によって読み出すことができる。また、setq-defaultによって、Current Bufferで読み出される値を設定することができる。


make-variable-buffer-local

(make-variable-buffer-local 'x)

を評価すると、(setq x 1)などとしてxに値をセットしたとき、xが自動的に(現在のバッファにおいて)バッファローカルになる。この、自動的にバッファローカルにする効果は、すべてのバッファにおいて有効になる。


例1

この例は、Emacsを再起動してから評価することを推奨する。

(setq x1 'default-value)

(make-variable-buffer-local 'x1)

(local-variable-p 'x1) ; => nil
x1 ; => default-value

(setq x1 'buffer-local-value)
(local-variable-p 'x1) ; => t
x1 ; => buffer-local-value

(make-variable-buffer-local 'x1)を評価した後に、x1の値をセットすると、x1が自動的にバッファローカルになっている。


例2

この例は、Emacsを再起動してから評価することを推奨する。

(setq x2 'default-value)

(make-variable-buffer-local 'x2)

(with-temp-buffer
(local-variable-p 'x2) ; => nil
x2 ; => default-value

(setq x2 'buffer-local-value)
(local-variable-p 'x2) ; => t
x2 ; => buffer-local-value
)

(local-variable-p 'x2) ; => nil
x2 ; => default-value

(make-variable-buffer-local 'x2)を評価したバッファとは、異なるバッファの上でx2に値をセットしても、x2は(移ったバッファにおいて)バッファローカルになる。


letによるシャドウイング

(make-variable-buffer-local 'x)を評価したのち、letなどでxをシャドウした上で値をセットしても、xはバッファローカルにならない。


例3

この例は、Emacsを再起動してから評価することを推奨する。

(setq x3 'default-value)

(make-variable-buffer-local 'x3)

(let ((x3 'default-value-in-let-1))
(local-variable-p 'x3) ; => nil
x3 ; => default-value-in-let-1

(setq x3 'default-value-in-let-2)
(local-variable-p 'x3) ; => nil
x3 ; => default-value-in-let-2
)

(make-variable-buffer-local 'x3)を評価したあとで、letによってx3に値をセットしているが、x3はバッファローカルにならない。また、シャドウした後のx3に値をセットしても、x3はバッファローカルにならない。


例4

この例は、Emacsを再起動してから評価することを推奨する。

(let ((x4 'default-value-in-let))

(make-variable-buffer-local 'x4)
(setq x4 'buffer-local-value))

(local-variable-p 'x4) ; => t
x4 ; => buffer-local-value

ローカルな変数にmake-variable-buffer-localを適用しても、変数をシャドウせずにセットすれば、バッファローカルになる。ただし、これは邪悪なコードらしく、評価すると


Making x4 buffer-local while let-bound!


という警告がエコーエリアに表示される。


注意すべきは、

(make-variable-buffer-local 'x)

を評価した時点では、xはバッファローカルにならないことである。make-variable-buffer-local(変数をバッファローカルにする)という名前は、この点で誤解しやすい。そのため、筆者はinit.elで

(defalias 'make-variable-autolocal 'make-variable-buffer-local)

と設定している。