F#でのリアクティブプログラミングの経験はありませんが、純粋関数型システムでのグローバル状態の問題は非常に一般的であり、非常に洗練されたソリューションがあります:モナド。
モナド自体は主にHaskellで使用されますが、基本的な概念により、計算式としてF#になりました。
アイデアは、実際に状態を変更するのではなく、状態の遷移、つまり新しい状態を生成する方法を説明するだけであるということです。状態自体は、プログラムで完全に隠すことができます。特別なモナディック構文を使用することにより、純粋でありながらステートフルなプログラムをほぼ必須に作成できます。
このソースから(変更された)実装を取得すると、State
モナドは次のようになります。
let (>>=) x f =
(fun s0 ->
let a,s = x s0
f a s)
let returnS a = (fun s -> a, s)
type StateBuilder() =
member m.Delay(f) = f()
member m.Bind(x, f) = x >>= f
member m.Return a = returnS a
member m.ReturnFrom(f) = f
let state = new StateBuilder()
let getState = (fun s -> s, s)
let setState s = (fun _ -> (),s)
let runState m s = m s |> fst
例を見てみましょう。続行中に値をログ(リストのみ)に書き込むことができる関数を記述します。したがって、次のように定義します
let writeLog x = state {
let! oldLog = getState // Notice the ! for monadic computations (i.e. where the state is involved)
do! setState (oldLog @ [x]) // Set new state
return () // Just return (), we only update the state
}
内state
では、ログリストを手動で処理しなくても、これを命令構文で使用できるようになりました。
let test = state {
let k = 42
do! writeLog k // It's just that - no log list we had to handle explicitly
let b = 2 * k
do! writeLog b
return "Blub"
}
let (result, finalState) = test [] // Run the stateful computation (starting with an empty list)
printfn "Result: %A\nState: %A" result finalState
それでも、ここではすべてが純粋に機能しています;)