我可以在F#中同时使用不同的工作流程吗?

时间:2013-10-02 02:53:37

标签: f# monads monad-transformers state-monad

我需要传递状态,同时能够使用可能的工作流链接功能。有两种工作流共享相同的上下文的方法吗?如果不是,这样做的方法是什么?

更新:

好吧,我有一个状态,表示我要在数据库中创建的实体的可用ID段。因此,一旦获得了ID,就必须将状态转换为具有下一个可用ID的较新状态并丢弃,以便没有人可以再次使用它。我不想为了惯用而改变国家。国家monad看起来像是一种方式,因为它隐藏了转变并传递了状态。一旦状态工作流程到位,我就无法使用Maybe工作流程,这是我到处使用的东西。

3 个答案:

答案 0 :(得分:4)

如前面的答案中所述,在F#(Haskell中的Monads)中组合工作流的一种方法是使用一种名为Monad Transformers的技术。

在F#中,这非常棘手,here is a project处理该技术。

可以使用该库自动组合State和Maybe(选项)来编写上一个答案的示例:

#r @"C:\Packages\FSharpPlus.2.0.0-CI00075\FsControl.dll"
#r @"C:\Packages\FSharpPlus.1.0.0-CI00018\FSharpPlus.dll"

open FSharpPlus

// 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)。

答案 1 :(得分:2)

在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可以通过使状态组件的类型通用来进一步推广。

答案 2 :(得分:1)

其他人已经直接回答了你的问题。但是,我认为问题的陈述方式导致了一个解决方案,从F#的角度来看并不是非常惯用 - 只要你是唯一一个从事代码工作的人,这可能对你有用,但我建议你不要这么做

即使添加了细节,问题仍然相当普遍,但这里有两个建议:

  • F#中合理使用的可变状态没有任何问题。例如,创建一个生成ID并将其传递的函数是完全正常的:

    let createGenerator() = 
      let currentID = ref 0
      (fun () -> incr currentID; !currentID)
    
  • 在构建实体时,您真的需要生成ID吗?听起来你可以只生成一个没有ID的实体列表,然后用Seq.zip压缩带有ID列表的最终实体列表。

  • 对于可能的计算,您是使用它来处理常规,有效状态还是处理异常状态? (这听起来像是第一种,这是正确的做事方式 - 但如果你需要处理真正的异常状态,那么你可能想要使用普通的.NET异常。)