【问题标题】:Can I use different workflows simultaneously in F#?我可以在 F# 中同时使用不同的工作流吗?
【发布时间】:2013-10-02 02:53:37
【问题描述】:

我需要传递我的状态,同时能够将函数与可能的工作流程链接起来。有没有办法让 2 个工作流共享相同的上下文?如果不是,那是怎么做的?

更新:

嗯,我有一个状态,它代表我要在数据库中创建的实体的一段可用 ID。因此,一旦获取了 ID,就必须将状态转换为具有下一个可用 ID 的更新状态并丢弃,以便没有人可以再次使用它。我不想为了习惯而改变状态。 State monad 看起来像是一种方法,因为它隐藏了转换并传递状态。一旦状态工作流程到位,我就无法使用我在任何地方都使用的 Maybe 工作流程。

【问题讨论】:

  • 在 F# 中,计算表达式通常不以这种方式使用。如果您提供有关您要解决的实际问题的更多信息,那么有人可能会建议一个替代的、更惯用的解决方案。
  • 不知道更多就很难说,但 F# 直接支持(可变)状态和异常,因此可能不需要使用计算表达式来处理这些。
  • @TomasPetricek,我刚刚添加了一些细节

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


【解决方案1】:

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

在 F# 中这真的很棘手,here is a project 处理该技术。

可以通过使用该库自动组合 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)。

【讨论】:

    【解决方案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 可以通过使状态组件的类型泛化来进一步泛化。

    【讨论】:

      【解决方案3】:

      其他人已经直接回答了您的问题。但是,我认为提出问题的方式会导致从 F# 的角度来看不是很惯用的解决方案 - 只要您是唯一处理代码的人,这可能对您有用,但我建议不要这样做.

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

      • 在 F# 中合理使用可变状态并没有错。例如,创建一个生成 ID 并将其传递的函数是非常好的:

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

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

      【讨论】:

      • 我想过改变状态,这就是我最终会得到的结果,这可能是因为 monad-transformers 令人生畏并且看起来有点矫枉过正。最后压缩 ID 似乎不是一个选项,因为它是一个实体图,ID 用作设置该图中关系的外键。因此,在将它们发送到数据库之前,必须将它们全部连接起来。我可以使用某种代理 ID,它可以在压缩时用真实 ID 替换,但这似乎需要更多工作。至于也许,我明白你的意思,不用担心,也许工作流程会处理有效状态。
      猜你喜欢
      • 2021-09-19
      • 2019-05-07
      • 1970-01-01
      • 1970-01-01
      • 2011-12-08
      • 2016-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多