我需要传递我的状态,同时能够将函数与可能的工作流程链接起来。有没有办法让 2 个工作流共享相同的上下文?如果不是,那是什么方法呢?
更新:
好吧,我有一个状态,它代表我要在数据库中创建的实体的一段可用 ID。因此,一旦获取了 ID,就必须将状态转换为具有下一个可用 ID 的更新状态并丢弃,以便没有人可以再次使用它。我不想为了习惯而改变状态。State monad 看起来像是一种方法,因为它隐藏了转换并传递状态。一旦状态工作流程到位,我就无法使用我在任何地方都使用的 Maybe 工作流程。
我需要传递我的状态,同时能够将函数与可能的工作流程链接起来。有没有办法让 2 个工作流共享相同的上下文?如果不是,那是什么方法呢?
更新:
好吧,我有一个状态,它代表我要在数据库中创建的实体的一段可用 ID。因此,一旦获取了 ID,就必须将状态转换为具有下一个可用 ID 的更新状态并丢弃,以便没有人可以再次使用它。我不想为了习惯而改变状态。State monad 看起来像是一种方法,因为它隐藏了转换并传递状态。一旦状态工作流程到位,我就无法使用我在任何地方都使用的 Maybe 工作流程。
如上一个答案所述,在 F#(Haskell 中的 Monads)中组合工作流的一种方法是使用一种称为 Monad Transformers 的技术。
在 F# 中,这真的很棘手,这里有一个处理该技术的项目。
可以通过使用该库自动组合 State 和 Maybe(选项)来编写上一个答案的示例:
#r @"c:\packages\FSharpPlus-1.0.0\lib\net45\FSharpPlus.dll"
open FSharpPlus
open FSharpPlus.Data
// Stateful computation
let computation =
monad {
let! x = get
let! y = OptionT (result (Some 10))
do! put (x + y)
let! x = get
return x
}
printfn "Result: %A" (State.eval (OptionT.run computation) 1)
所以这是另一种选择,而不是创建您的自定义工作流程,而是使用将自动推断的通用工作流程(a-la Haskell)。
在 F# 中,您不能像在 Haskell 中那样通过使用Monad Transformers或类似技术轻松混合不同类型的计算表达式。但是,您可以构建自己的 Monad,嵌入状态线程和可选值,如下所示:
type StateMaybe<'T> =
MyState -> option<'T> * MyState
// Runs the computation given an initial value and ignores the state result.
let evalState (sm: StateMaybe<'T>) = sm >> fst
// Computation expression for SateMaybe monad.
type StateMaybeBuilder() =
member this.Return<'T> (x: 'T) : StateMaybe<'T> = fun s -> (Some x, s)
member this.Bind(sm: StateMaybe<'T>, f: 'T -> StateMaybe<'S>) = fun s ->
let mx,s' = sm s
match mx with
| Some x -> f x s'
| None -> (None, s)
// Computation expression builder.
let maybeState = new StateMaybeBuilder()
// Lifts an optional value to a StateMaybe.
let liftOption<'T> (x: Option<'T>) : StateMaybe<'T> = fun s -> (x,s)
// Gets the current state.
let get : StateMaybe<MyState> = fun s -> (Some s,s)
// Puts a new state.
let put (x: MyState) : StateMaybe<unit> = fun _ -> (Some (), x)
这是一个示例计算:
// Stateful computation
let computation =
maybeState {
let! x = get
let! y = liftOption (Some 10)
do! put (x + y)
let! x = get
return x
}
printfn "Result: %A" (evalState computation 1)
StateMaybe
可以通过使状态组件的类型通用化来进一步推广。
其他人已经直接回答了你的问题。但是,我认为提出问题的方式会导致从 F# 的角度来看不是很惯用的解决方案 - 只要您是唯一处理代码的人,这可能对您有用,但我建议不要这样做.
即使添加了细节,这个问题仍然相当笼统,但这里有两个建议:
在 F# 中合理使用可变状态没有任何问题。例如,创建一个生成 ID 并将其传递的函数是非常好的:
let createGenerator() =
let currentID = ref 0
(fun () -> incr currentID; !currentID)
您真的需要在构建实体时生成 ID 吗?听起来您可以只生成一个没有 ID 的实体列表,然后用于Seq.zip
压缩具有 ID 列表的最终实体列表。
至于可能的计算,你是用它来处理常规的、有效的状态,还是处理异常的状态?(这听起来像是第一种,这是正确的做事方式——但如果您需要处理真正的异常状态,那么您可能想要使用普通的 .NET 异常)。