F#中的组合单子

Bea*_*epp 12 monads f# functional-programming monad-transformers

我试图让我的头围绕F#中的monads,我正在寻找一个组合它们的例子.

在haskell中看起来你会使用Monad Transformers但在F#中看起来你会创建自己的计算表达式构建器.

我可以支持这一点,但有没有标准monad的一些组合的例子以及如何使用它们?

我特别感兴趣的是将Reader,Writer和Either结合起来构建接受环境的函数,调整它,然后使用Writer将更改返回到发生的环境中.两者都可用于区分成功与失败.

现在,获得一个产生值+ log或错误的EitherWriter计算表达式的例子会很棒.

scr*_*wtp 6

写"组合拳"建设者将是你会怎么做,在F#如果你做到这一点.然而,这不是典型的方法,当然也不是实用的方法.

在Haskell中,你需要monad变换器,因为Haskell中无处不在的monad是如此.F#不是这种情况 - 这里计算工作流程是一个有用的工具,但只是一个补充工具.首先--F#并没有禁止副作用,因此使用monad的一个重要原因就是消失了.

典型的方法是识别捕获您想要建模的计算本质的工作流程(在您的情况下,它似乎是Either monad)并使用其他方法来完成剩下的工作 - 比如线程化修改后的"环境"通过计算作为值或使用副作用进行日志记录(又名"日志框架").


The*_*ght 5

我将告诉你如何创建一个EitherWriter,有两种方法可以去根据建筑的其中之一,你如何订购EitherWriter,但我要表明,似乎最像你期望的工作流程的例子.

我也将简化编写器,使其仅记录到string list.更全面的编写器实现将使用memptymappend抽象适当的类型.

类型定义:

type EitherWriter<'a,'b>  = EWriter of string list * Choice<'a,'b>
Run Code Online (Sandbox Code Playgroud)

基本功能:

let runEitherWriter = function
    |EWriter (st, v) -> st, v

let return' x = EWriter ([], Choice1Of2 x)

let bind x f =
    let (st, v) = runEitherWriter x
    match v with
    |Choice1Of2 a -> 
        match runEitherWriter (f a) with
        |st', Choice1Of2 a -> EWriter(st @ st', Choice1Of2 a)
        |st', Choice2Of2 b -> EWriter(st @ st', Choice2Of2 b)
    |Choice2Of2 b -> EWriter(st, Choice2Of2 b)
Run Code Online (Sandbox Code Playgroud)

我喜欢在独立模块中定义这些,然后我可以直接使用它们或引用它们来创建计算表达式.同样,我将保持简单,只做最基本的可用实现:

type EitherWriterBuilder() =
    member this.Return x = return' x
    member this.ReturnFrom x = x
    member this.Bind(x,f) = bind x f
    member this.Zero() = return' ()

let eitherWriter = EitherWriterBuilder()
Run Code Online (Sandbox Code Playgroud)

这有什么用的吗?

F#的乐趣和利润有一些关于铁路导向编程的重要信息,以及它与竞争方法相比带来的优势.

这些示例基于自定义,Result<'TSuccess,'TFailure>但当然,它们同样可以使用F#的内置Choice<'a,'b>类型来应用.

虽然我们可能会遇到以这种面向铁路的形式表达的代码,但我们更不可能遇到预先编写的代码,可以直接使用EitherWriter.因此,该方法的实用性取决于从简单的成功/失败代码容易地转换成与上述monad兼容的东西.

以下是成功/失败功能的示例:

let divide5By = function
    |0.0 -> Choice2Of2 "Divide by zero"
    |x -> Choice1Of2 (5.0/x)
Run Code Online (Sandbox Code Playgroud)

此功能只用提供的数字除以5.如果该数字为非零,则返回包含结果的成功,如果提供的数字为零,则返回失败,告诉我们我们已尝试除以零.

我们现在需要一个辅助函数来将这样的函数转换为可用于我们的函数EitherWriter.一个可以做到这一点的功能是这样的:

let eitherConv logSuccessF logFailF f = 
    fun v ->
        match f v with
        |Choice1Of2 a -> EWriter(["Success: " + logSuccessF a], Choice1Of2 a)
        |Choice2Of2 b -> EWriter(["ERROR: " + logFailF b], Choice2Of2 b)
Run Code Online (Sandbox Code Playgroud)

它需要一个描述如何记录成功的函数,一个描述如何记录失败的函数和一个Eithermonad的绑定函数,它返回一个monad的绑定函数EitherWriter.

我们可以像这样使用它:

let ew = eitherWriter {
    let! x = eitherConv (sprintf "%f") (sprintf "%s") divide5By 6.0
    let! y = eitherConv (sprintf "%f") (sprintf "%s") divide5By 3.0
    let! z = eitherConv (sprintf "%f") (sprintf "%s") divide5By 0.0
    return (x, y, z)
}

let (log, _) = runEitherWriter ew

printfn "%A" log
Run Code Online (Sandbox Code Playgroud)

然后返回:

["成功:0.833333"; "成功:1.666667"; "错误:除以零"]


Gus*_*Gus 5

我知道在F#中通常不认为它是惯用的,但对于好奇的读者,这里是使用F#+的@TheInnerLight答案:

#r @"FSharpPlus.1.0.0-CI00089\lib\net40\FSharpPlus.dll"

open FSharpPlus

let divide5By = function
    |0.0 -> Choice2Of2 "Divide by zero"
    |x   -> Choice1Of2 (5.0/x)

let eitherConv logSuccessF logFailF f v = 
    ErrorT (
        match f v with
        | Choice1Of2 a -> Writer(Choice1Of2 a, ["Success: " + logSuccessF a])
        | Choice2Of2 b -> Writer(Choice2Of2 b, ["ERROR: "   + logFailF b]  ))

let ew = monad {
    let! x = eitherConv (sprintf "%f") (sprintf "%s") divide5By 6.0
    let! y = eitherConv (sprintf "%f") (sprintf "%s") divide5By 3.0
    let! z = eitherConv (sprintf "%f") (sprintf "%s") divide5By 0.0
    return (x, y, z)
}

let (_, log) = ew |> ErrorT.run |> Writer.run
Run Code Online (Sandbox Code Playgroud)

当然,这适用于任何半身像。

这种方法基本上是Haskell方法,变压器可以与任何monad一起使用,并且在上面的代码中,您可以轻松地切换至OptionT,替换Choice1Of2SomeChoice2Of2None这样就可以正常工作。

我个人更喜欢首先使用这种方法,它写起来容易得多,当然也更短。一旦有了所需的功能,就可以自定义我的转换器,或者如果它对我要解决的问题足够好,请保留原样。