Hatena::ブログ(Diary)

すにぺっと

2011-04-20

clojureプログラミング入門-29 varでスレッドごとの状態を管理

| 13:37 | clojureプログラミング入門-29 varでスレッドごとの状態を管理を含むブックマーク

defやdefnは動的なvarをつくる。

今までの例では、defに初期値を与えてvarのルート束縛を

つくっていた。

(def foo 10)

このfooの束縛はすべてのスレッドで共有されるが、

bindingマクロを使用するとvarにスレッドローカルな束縛を

作れる。

(binding [bindings] & body)

この束縛は実行したスレッドがbindingのbodyを抜けるまで

スレッドのあらゆる場所から参照可能。

他のスレッドにはこの束縛が見えない。

user=> (def foo 10)
#'user/foo
user=> (binding [foo 43] foo)
43
user=> (defn print-foo [] (println foo))
#'user/print-foo
user=> (let [foo "let foo"] (print-foo)) ;1
10
nil
user=> (binding [foo "bound foo"] (print-foo)) ;2
bound foo
nil

1.letは自分の外には影響を及ぼさない。

2.bindingでは、束縛の影響はbodyから呼び出されるコードに及ぶ。


離れたところから影響を及ぼす

動的な束縛を持たせるためのvarは、スペシャル変数と呼ばれる。

スペシャル変数は慣習的に、名前の最初と最後に*をつける。

clojureの標準入出力ストリーム、*out*,*err*,*in*など。

これらはスレッド毎に設定するため、動的束縛を使用している。

動的束縛は離れたところから影響を及ぼすことを可能にする。

一時的に関数に機能を追加する場合など。

これはアスペクト指向プログラミングと呼ばれる物に近い。

例として、0.1秒まってから引数を2倍する関数を書く。

user=> (defn slow-double[n]
   (Thread/sleep 100)
   (* n 2))
#'user/slow-double
user=> (defn calls-slow-double [] (map slow-double [1 2 1 2 1 2]))
#'user/calls-slow-double
user=> (time (dorun (calls-slow-double)))
"Elapsed time: 601.071 msecs"
nil

そしてコレクションにslow-doubleを適用する

calls-slow-doubleを書く。

timeで時間を測ると0.6秒程度かかる。

(dorunで直ぐに実行している点に注意)

これは何度やっても結果は同じ。

これをメモ化すれば効率がよくなるので、memorizeを使用する。

動的束縛を使用し、

call-slow-double内で呼び出しているslow-doubleを

メモ化するようにする。

user=> (defn demo-memoize[] 
  (time (dorun 
		 (binding [slow-double (memoize slow-double)] 
		 (calls-slow-double)))))
#'user/demo-memoize
user=> (demo-memoize)
"Elapsed time: 200.898 msecs"
nil

0.2秒程度で処理が終わる。

ただ、これは外部から関数の動作を変更するという

便利ではあるが危険なことを実施している。

注意して使用すること。


JAVAのコールバックAPIを使う

JAVAAPIで、swingGUIフレームワークなど、

イベントハンドラをコールバックするように設計されている。

これらのコールバックインターフェイスは、変更可能なオブジェクトであることを

前提に設計されている。また、シングルスレッド動作が多い。

そういう場合、clojureでは動的束縛を使用する。

clojureスレッドローカル束縛を変更するにはset!を使用。

(set! var-symbol new-val)

しかし、できるだけ避けるべき。

clojureでset!が使用されているのは一箇所だけで、

SAXのContentHandlerのみ。

これは、オブジェクトは変更可能であり、プログラムは並行動作のことを

考えずにかかれているという前提で書く必要がある。

clojureは特殊な場合としてこのスタイルを許容しているが、

通常はJAVAとやりとりするときだけ使用すべき。


並行モデルのまとめ

モデル用途更新中に使える関数
refとSTM協調的・同期的な更新副作用なし
アトム非協調的・同期的な更新副作用なし
エージェント非協調的・非同期的な更新すべて
varスレッドローカルな動的スコープすべて
Javaのロック協調的・同期的な更新すべて

ひととおり基本的機能はおわったので、

次は第9章でテストとかフレームワークとか。