- 作成: ref
- 参照: deref または @
- 変更: dosync で包んで
- ref-set: 上書き
- alter: 関数を適用して再代入(順序を保証)
- commute: 関数を適用して再代入(順序保証なし)
作成
(def x (ref '(1 2 3)))
参照
(deref x) ; -> (1 2 3)
@x ; -> (1 2 3)
変更/上書き
ref の変更は dosync で包むことで, 複数の変更のアトミシティが保証されるが, 単独の変更の場合でも, dosync で包む必要がある.
(ref-set x '(2 3 4))
; -> java.lang.IllegalStateException: No transaction running (NO_SOURCE_FILE:0)
(dosync (ref-set x '(2 3 4)) ; -> (2 3 4)
@x ; -> (2 3 4)
dosync で包めば複数の変更が外からアトミックに見える.
(def y (ref (count @x)))
(dosync
(ref-set x (cons 8 @x))
(ref-set y (count @x)))
上記で「x は変更されたが, x の変更に y が追随していない」という状態が他のスレッドから見えないことが保証される.
変更/関数を適用して再代入
r が ref の時
(alter r fn args...)
は
(ref-set r (fn @r args...))
と等しい.
従って, cons ではなく conj を使うと引数順序が適合する.
(dosync
(alter x conj 9)
(ref-set y (count @x)))
dosync と alter は
MVCC (Multiversion Concurrency Control, 多版型同時実行制御)
で動いている.
トランザクションはローカルコピーに対する変更を行い
その間に他のトランザクションが ref を書き換えていたら
dosync は再起動される.
(dosync から例外で抜けた場合はこの限りではない)
commute は更新順序が違ってもいい場合 使い方は alter と同じ
バリデータ
ref を作るときに :validater キーワードで引数を一つ取り, bool 値を返す関数を指定しておけば, ref の更新時に値にその関数を 適用して false を返すならば例外を投げてくれる
(def x (ref () :validator (partial every? even?)))
(dosync (alter x conj 2)) ; -> (2)
(dosync (alter x conj 3))
; -> java.lang.IllegalStateException: Invalid reference state (NO_SOURCE_FILE:0)
(dosync (alter x conj 4)) ; -> (4 2)
詳細はこちらのスライド が詳しいです.
ref は dosync で包み, 複数の変更を協調させて使う. atom は他の ref や atom と協調する必要が無いときに使う.
- 作成: atom
- 参照: deref または @
- 変更:
- reset!: 上書き
- swap!: 関数を適用して再代入
ref と同様 :validator が使える
(def x (atom () :validator (partial every? even?))) ; -> #'user/x
@x ; -> ()
(reset! x (conj @x 2)) ; -> (2)
(reset! x (conj @x 3)) ; -> java.lang.IllegalStateException: ...
(reset! x (conj @x 4)) ; -> (4 2)
(swap! x conj 5) ; -> java.lang.IllegalStateException: ...
(swap! x conj 6) ; -> (6 4 2)
関数が返った時点で値の更新が保証されなくていいなら(遅れていいなら)
- 作成: agent
- 参照: deref または @
- 変更:
- send: 関数を適用して再代入
上書き変更 (ref-set や reset! のような) は提供されていない.
:validator は ref や atom と同様に使えるが,
例外は send が呼ばれたときではなく,
後の更新操作時に RuntimeException: Agent is failed を返す.
(def x (agent () :validator (partial every? even?))) ; -> #'user/x
@x ; -> ()
(send x conj 2) ; -> #<Agent@.... (2)>
@x ; -> (2) 反映が遅れて () が返るかもしれない.
(send x conj 3) ; -> #<Agent@.... (3)>
@x ; -> (2)
(send x conj 4) ; -> java.lang.RuntimeException: Agent is failed...
エラーが起きていたかどうかは agent-errors で調べられる.
(agent-errors x) ; -> (#<IllegalStateException java.lang.Illegal...>)
clear-agent-errors で復帰する.
(clear-agent-errors x) ; -> (2)
(send x conj 4) ; -> #<Agent@.... (4 2)>
@x ; -> (4 2) <- くどいようだがこれは保証されない.
await で全ての send された変更が反映されるのを待つことができる.
(def x (agent () :validator (partial every? even?))) ; -> #'user/x
(send x conj 2) ; -> #<Agent@.... (2)>
(send x conj 4) ; -> #<Agent@.... (4 2)>
(await x) ; -> nil (これはブロックする.)
@x ; -> (4 2) これは保証される.
更新の反映を確認してブロックせずに返すには await-for を使い, ミリ秒でタイムアウトを指定する.
(send x conj 6) ; -> #<Agent@.... (6 4 2)>
(send x conj 8) ; -> #<Agent@.... (6 4 2)>
(await 100 x) ; -> タイムアウトしたら nil, そうでなければ nil 以外を返す.
@x ; -> (8 6 4 2)
await の返り値が nil 以外なら @x
は (8 6 4 2)
であることが保証される.
await の返り値が nil なら @x
は (4 2)
かもしれないし, (6 4 2)
かもしれない.