我对 F# 下的响应式编程没有任何经验,但是纯函数系统中的全局状态问题很常见,并且有一个非常优雅的解决方案:Monads。
虽然 monad 本身主要在 Haskell 中使用,但其基本概念已作为计算表达式进入 F# 。
这个想法是你实际上并没有改变状态,而只是描述状态的转换,即如何产生新的状态。状态本身可以完全隐藏在程序中。通过使用特殊的 monadic 语法,您几乎可以命令式地编写纯粹但有状态的程序。
从此源获取(修改的)实现,State
monad 可能看起来像这样
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
尽管如此,这里的一切都是纯粹的功能;)