F# F中的组合单体#

F# F中的组合单体#,f#,functional-programming,monads,monad-transformers,F#,Functional Programming,Monads,Monad Transformers,我正试着了解F#中的单子,并寻找一个创作单子的例子 在haskell中,您可能会使用Monad Transformers,但在F#中,您可能会创建自己的计算表达式生成器 我可以理解这一点,但是否有一些标准单子组合的例子,以及如何使用它们 我特别感兴趣的是将Reader、Writer和两者结合起来,以构建环境中的函数,对其进行调整,然后使用Writer将发生的更改返回到环境中。两者都可以用来区分成功与失败 现在,最好能获得一个EitherWriter计算表达式的示例,该表达式会生成一个值+日志或一

我正试着了解F#中的单子,并寻找一个创作单子的例子

在haskell中,您可能会使用Monad Transformers,但在F#中,您可能会创建自己的计算表达式生成器

我可以理解这一点,但是否有一些标准单子组合的例子,以及如何使用它们

我特别感兴趣的是将Reader、Writer和两者结合起来,以构建环境中的函数,对其进行调整,然后使用Writer将发生的更改返回到环境中。两者都可以用来区分成功与失败

现在,最好能获得一个EitherWriter计算表达式的示例,该表达式会生成一个值+日志或一个错误。

如果要在F#中编写一个“组合”构建器,您应该这样做。然而,这不是一种典型的方法,当然也不是一种实用的方法

在哈斯凯尔,你需要单子变形金刚,因为在哈斯凯尔,单子无处不在。F#的情况并非如此——在这里,计算工作流是一个有用的工具,但只是一个补充工具。首先也是最重要的一点——F#并不禁止副作用,所以使用单子的一个重要原因就在这里


典型的方法是确定工作流,该工作流捕获了您要建模的计算的本质(在您的情况下,它似乎是一个monad),并使用其他方法来完成其余的工作,例如将修改后的“环境”作为值贯穿计算,或使用副作用进行日志记录(也称为“日志框架”).

我将展示如何创建一个编写器。根据您对
编写器的排序方式,有两种方法可以创建其中一种编写器,但我将展示一个最类似于您所需工作流的示例

我还将简化编写器,使其只记录到
字符串列表
。更完整的writer实现将使用
mempty
mappend
对适当的类型进行抽象

类型定义:

type EitherWriter<'a,'b>  = EWriter of string list * Choice<'a,'b>
我喜欢在一个独立的模块中定义它们,然后我可以直接使用它们或引用它们来创建计算表达式。同样,我将保持它的简单性,只做最基本的可用实现:

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()

这有什么实用性吗?

F#for fun and price提供了一些非常好的信息,以及与竞争方法相比它所带来的优势

这些示例基于自定义的
结果
,但当然,它们同样可以使用F#的内置
选项
类型应用

虽然我们可能会遇到以这种面向铁路的形式表示的代码,但我们不太可能遇到预先编写的代码,以便直接与
EitherWriter
一起使用。因此,这种方法的实用性取决于从简单的成功/失败代码到与上述monad兼容的代码的轻松转换

下面是一个成功/失败函数的示例:

let divide5By = function
    |0.0 -> Choice2Of2 "Divide by zero"
    |x -> Choice1Of2 (5.0/x)
此函数仅将5除以提供的数字。如果该数字不为零,则返回一个包含结果的成功,如果提供的数字为零,则返回一个失败,告诉我们已尝试除以零

我们现在需要一个helper函数来将这样的函数转换为可在
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)
它使用一个描述如何记录成功的函数,一个描述如何记录失败的函数,以及一个用于
单子的绑定函数,并为
编写器
单子返回一个绑定函数

我们可以这样使用它:

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
然后返回:

[“成功:0.833333”;“成功:1.66666 7”;“错误:被零除”]


我知道这在F#中通常不被认为是惯用语,但对于好奇的读者来说,这里有一个“他们的正确答案”,使用:

当然这一个适用于任何幺半群

这种方法基本上是Haskell方法,变形金刚可以与任何单子一起工作,在上面的代码中,您可以轻松切换到
option
,将
Choice1Of2
替换为
Some
,将
Choice2Of2
替换为
None
,这样就可以工作了


就我个人而言,我更喜欢先使用这种方法,它更容易编写,当然也更短。一旦我有了所需的功能,我就可以定制我的变压器,或者如果它对我要解决的问题足够好的话,我可以保持原样。

IIRC有一些。我还提供了一个例子:这里有一个in F#。另一种方法是通过F#中的这个来实现您所描述的。我没有您想要的东西,但是这个小项目使用了一个组合的阅读器/状态monad:它是本文中一些(Haskell)代码的F#翻译:另外,我正在开发的IO库(用于F#中的monadic IO)实现了许多更复杂的一元操作,如一元循环,并且有很好的文档记录:我使用F#monad transformers from来快速原型化我正在寻找的内容,然后,有一个工作示例,我可以保持原样,也可以自定义转换后的单元。顺便说一句,有许多变压器使用
async,这当然是获得可行解决方案的更快方法!是否有使用mappend/mempty或某种接口来定义它们的示例?@user1570690这里有一个抽象类示例:反向情况如何,一种面向铁路的monad,即状态+日志或失败?@user1570690在这种情况下,您需要一个带有两个联合情况的自定义类型,一个表示成功,一个表示失败,其值和状态包含在成功案例中。然后需要一个匹配该类型的绑定函数。
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
#r @"FSharpPlus.1.0.0\lib\net45\FSharpPlus.dll"

open FSharpPlus
open FSharpPlus.Data

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

let eitherConv logSuccessF logFailF f v = 
    ChoiceT (
        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 |> ChoiceT.run |> Writer.run