bindingとvarについて
bindingってなんだろう? プログラミングClojureを読んでいまいちピンと来なかったので考えてみました。
bindingは新しい変数を作らない
;; program-1 (let [x 10] x) ;=> 10 (binding [x 10] x) ;=> java.lang.Exception: Unable to resolve var: x in this context...
let はレキシカル変数を束縛する。新しい変数 x が作られたことになる。let式内では新たに作られたレキシカル変数 x の値を参照する。
binding はレキシカル変数を束縛しない。新しい変数 x は作られない。binding式内で x を参照しても x という変数は存在しないのでエラーになる。
binding式はlet式とよく似てるので 10 に束縛された x が存在しているように見えるかもしれない(自分はそう錯覚した)けれど新らたな変数は作られないらしい。let式とは全く違う。
動的なvarのルート束縛
var ってなんだ? ルート束縛ってなんだ? わかりにくいですね。
要するに def で名前束縛をすると「動的なvar」というモノが新たに作られ、これに値を束縛することを「ルート束縛」と呼ぶらしい。
;; program-2 (def x 100) (let [x 10] x) ;=> 10 (binding [x 10] x) ;=> 10 x ;=> 100
def により、x をルート束縛しました。この x は動的なvarとよばれるモノに該当します。
let式の結果は program-1 から変化ありません。100にルート束縛された x は、レキシカル変数 x によって名前が隠されてしまうため let式内の x は 10 と評価されます。
一方binding式は状況が変りました。エラーにならず評価値 10 を返しています。
binding式は新たな変数を作りません。では式の中の x は何者かというとこれは def でルート束縛された x だと考えるしかないでしょう。つまり binding はルート束縛されている x の値を 10 に書き換え(あるいは再束縛?)していることになります。ただしその後の x の評価値が 100 になっていることから、binding による再束縛の有効範囲は binding式内に限られることが分ります。この仕様は次のように書くとよりハッキリします。
;; program-3 (def x 100) ;=> user/x が 100 に束縛(ルート束縛)される。 (let [x 10] [x user/x (identical? x user/x)]) ;=> [10 100 false] (binding [x 10] [x user/x (identical? x user/x)]) ;=> [10 10 true] x ;=> 100
clojureでは ns式により名前空間を作ることができます。名前空間の宣言を省略するとデフォルト名前空間として「user」が適用されます。そして変数は「名前空間/変数名」のように名前修飾を付けて参照することが出来ます。
また def で束縛された名前は、束縛がおこなわれた名前空間に属します。「ルート束縛」という言葉から「全ての名前空間の上位に『ルート名前空間』が存在しそこに属する」と勘違いしそうです(自分はそう誤解した)がそうではなく、あくまでも def 式が書かれたその名前空間に属します。ルート名前空間などという物はありません。
したがって program-3 の一行目は「user/x を 100 に束縛している」ことになります。
let式の中の無修飾の x はレキシカル変数であり、名前空間修飾を付けた user/x はルート束縛された x ということになります。評価値も期待通りです。オブジェクトの同一性を調べる identical? の評価値も両者が異なる変数であること(false)を示しています。
一方binding式は、x も user/x もどちらも評価値が 10 です。identical? も同一であること(true)を示しています。ルート束縛された user/x の値が書き換えられていることが明白です。
set!による書き換え
set! は変数の値を書き換えるフォームです。ただし書換えができるのは binding式の中に限られるようです。
;; program-4 ;; ルート束縛 (def x 100) (def y 200) ;; バインドした x は set! で書き換え可能 (binding [x 10] (do (print x "") (set! x 20) (print x ""))) ;=> 10 20 nil ;; user/x も結局同じ変数なので書き換え可能 (binding [x 10] (do (print user/x "") (set! user/x 20) (print user/x ""))) ;=> 10 20 nil ;; binding式の中でもバインドしていない変数(y)は書き換えできない (binding [x 10] (do (print y "") (set! y 20) (print y ""))) ;=> java.lang.IllegalStateException: Can't change/establish root binding of: y with set ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 以下すべて binding式ではないので set! はエラーになる。 (let [x 10] (do (print x " ") (set! x 20) (print x " "))) ;=> java.lang.IllegalArgumentException: Invalid assignment target ... (let [x 10] (do (print user/x " ") (set! user/x 20) (print user/x " "))) ;=> java.lang.IllegalStateException: Can't change/establish root binding of: x with set ... (set! x 20) ;=> java.lang.IllegalStateException: Can't change/establish root binding of: x with set ...
エラーメッセージを見返してみる
;; program-1 より (binding [x 10] x) ;=> java.lang.Exception: Unable to resolve var: x in this context...
ルート束縛のない変数をbinding しようとすると「varが解決できない」と怒られます。ルート束縛で作られるのは「動的なvar」でした。varが定義されていないので解決できなかったということですね。
;; program-4 より (def x 100) (set! x 20) ;=> java.lang.IllegalStateException: Can't change/establish root binding of: x with set ... (let [x 10] (do (print x " ") (set! x 20) (print x " "))) ;=> java.lang.IllegalArgumentException: Invalid assignment target ...
前者、establish は「根付く」「設置する」という意味。「ルート束縛された x は、値を変更できない」という意味でしょうか。
後者は ArgumentException。レキシカル変数を set! の引数にするとこうなるようです。
bindingによるスレッドローカル変数
binding式でバインドした変数はスレッドローカルになります。これについては各所で言及されているので詳細はいりませんね。