興味深いことに、通常のStateモナドはUpdateモナドの特別系 ではありません 。しかし、同様の機能 - 読み書き可能な状態を伴った計算 - を実装した計算を定義することはできます。
この最後の例では、 状態 を表す型 State
と 更新 を表現する型 Update
両方が、意味のある役割を持つことになります。それらの型を自身が保持している値に対してジェネリックにします。 State
は単に含んでいる値(現在の状態)のラッパーです。 Update
は2種類 - 空の更新(何もしない)と状態を設定する更新 - を取り得ます。
/// 型'Tの状態をラップします
type StateState<'T> = State of 'T
/// 型'Tの状態に対する更新を表します
type StateUpdate<'T> =
| Set of 'T | SetNop
/// 空の更新 - 何も状態を変更しません
static member Unit = SetNop
/// 更新の結合 - 最新の(最も右にある) 'Set'更新を返します
static member Combine(a, b) =
match a, b with
| SetNop, v | v, SetNop -> v
| Set a, Set b -> Set b
/// 状態に対して更新を適用します - 'Set'更新が状態を変更します
static member Apply(s, p) =
match p with SetNop -> s | Set s -> State s
この定義は前の2つに比べるとより興味深いものとなっています、なぜなら 状態 と 更新 の間にいくつかの相互作用があるからです。特に、更新が Set v
(現在の状態を新しいもので置き換えようとします) の時には、 Apply
メンバは元とは異なる状態を返します。 Unit
メンバについては、 単に元の状態を保持しておくという SetNop
の更新が必要になります。(ですのでこの場合は Apply
はただ単に元の値を返します。)
もう一つの特筆すべき点は、 Combine
操作 - 2つの更新(両方とも空の更新かも知れませんし通常の更新かもしれません)を受け取り一つの更新を返します - です。合成 a1 ++ a2 ++ .. ++ an
を状態更新のシーケンス( Set
でも SetNope
でも構いません )として読み取った場合、 Combine
操作はシーケンス中の最後の Set
更新(一つも Set
更新がない場合は SetNop
)を返します。言い換えると、シーケンス全体の中での最後の状態を設定するような更新を構築するのです。
さあ、型の定義はできましたので、通常のプリミティブを追加するのはかなり簡単になります。
/// 指定された値に状態を設定する
let set s = UM (fun _ -> (Set s,()))
/// 現在の状態を取得する
let get = UM (fun (State s) -> (SetNop, s))
/// 初期状態を設定して計算を実行する
let setRun s (UM f) = f (State s) |> snd
set
操作は一般的なStateモナドのそれとは少し違います。状態を無視し、新しい状態を設定するための計算を表す 更新 を構築します。 get
オペレーションは状態を読み取ってそれを返します - ただし何も変更しない場合には、更新として SetNop
を返します。
ここまで読んできた方なら、次の例がどんな風になるのか予想できるでしょう!もう一度 update { .. }
コンピュテーション式を使います。今回は、 demo5
という、状態をインクリメントし demo6
のループの中から呼ぶコンピュテーション式を定義します。
/// 状態を1ずつインクリメントする
let demo5 = update {
let! v = get
do! set (v + 1) }
/// demo5をループの中で反復して呼び
/// 最後の状態を返す
let demo6 = update {
for i in 1 .. 10 do
do! demo5
return! get }
// サンプルを初期状態 0 で実行させる
demo6 |> setRun 0
コードを実行すると、予想通り10という結果を得ます - ゼロから始まり、その状態を10回インクリメントしたわけです。 我々は UpdateBuilder
の定義を拡張しましたので(前の章で行いました),
いくつかの素敵な特典にタダ乗りで来ています - for
ループを使うことや、( demo5
のように)ただ状態を変更したいだけの際には、明示的に return
を書かなくても計算を記述することができるのです。