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

我可以在F#中同时使用不同的工作流吗?,f#,monads,monad-transformers,state-monad,F#,Monads,Monad Transformers,State Monad,我需要传递我的状态,同时能够将功能和工作流链接起来。有没有办法让两个工作流共享相同的上下文?如果没有,怎么做 更新: 我有一个状态,它表示我将在数据库中创建的实体的可用ID的一段。因此,一旦获得了一个ID,就必须将状态转换为具有下一个可用ID的较新状态,并将其丢弃,这样就没有人可以再次使用它。我不想为了习惯而改变状态。状态单子看起来像是一种方式,因为它隐藏了转换并传递状态。一旦状态工作流就位,我就不能使用Maybe工作流,这是我在任何地方都使用的 在F#中,您不能像在Haskell中那样,通过使

我需要传递我的状态,同时能够将功能和工作流链接起来。有没有办法让两个工作流共享相同的上下文?如果没有,怎么做

更新:

我有一个状态,它表示我将在数据库中创建的实体的可用ID的一段。因此,一旦获得了一个ID,就必须将状态转换为具有下一个可用ID的较新状态,并将其丢弃,这样就没有人可以再次使用它。我不想为了习惯而改变状态。状态单子看起来像是一种方式,因为它隐藏了转换并传递状态。一旦状态工作流就位,我就不能使用Maybe工作流,这是我在任何地方都使用的

在F#中,您不能像在Haskell中那样,通过使用Monad转换器或类似技术,轻松地混合不同类型的计算表达式。但是,您可以构建自己的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)
stateObay
可以通过使状态组件的类型通用化来进一步通用化。

在F#中,您不能像在Haskell中一样,通过使用Monad Transformer或类似技术轻松混合不同类型的计算表达式。但是,您可以构建自己的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)

StateMaybe
可以通过使状态组件的类型通用化来进一步通用化。

如前一个答案所述,在F#(Haskell中的Monad)中组合工作流的一种方法是使用一种称为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)中组合工作流的一种方法是使用称为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#的角度来看,这个问题的表述方式导致了一个不太惯用的解决方案——只要你是唯一一个编写代码的人,这可能对你有用,但我建议不要这样做

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

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

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

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


    • 其他人已经直接回答了您的问题。然而,我认为,从F#的角度来看,这个问题的表述方式导致了一个不太惯用的解决方案——只要你是唯一一个编写代码的人,这可能对你有用,但我建议不要这样做

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

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

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

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


      在F#中,计算表达式通常不以这种方式使用。如果你提供了更多关于你试图解决的实际问题的信息,那么有人可能会建议一个替代的、更惯用的解决方案。如果不知道更多,很难说,但F#直接支持(可变)状态和异常,所以可能不需要用计算表达式来处理这些问题。@TomasPetricek,我只是在F#中添加了一些细节,计算表达式通常不是这样使用的。如果你提供了更多关于你试图解决的实际问题的信息,那么有人可能会建议一个替代的、更惯用的解决方案。如果不知道更多,很难说,但F#直接支持(可变)状态和异常,因此,可能不需要用计算表达式来处理这些问题。@TomasPetricek,我只是添加了一些细节,我想对状态进行变异,这可能是因为monad Transformer令人望而生畏,看起来有些过分了。压缩末尾的ID似乎不是一个选项,因为它是一个实体和ID的图形