F# F中的组合单体#
我正试着了解F#中的单子,并寻找一个创作单子的例子 在haskell中,您可能会使用Monad Transformers,但在F#中,您可能会创建自己的计算表达式生成器 我可以理解这一点,但是否有一些标准单子组合的例子,以及如何使用它们 我特别感兴趣的是将Reader、Writer和两者结合起来,以构建环境中的函数,对其进行调整,然后使用Writer将发生的更改返回到环境中。两者都可以用来区分成功与失败 现在,最好能获得一个EitherWriter计算表达式的示例,该表达式会生成一个值+日志或一个错误。如果要在F#中编写一个“组合”构建器,您应该这样做。然而,这不是一种典型的方法,当然也不是一种实用的方法 在哈斯凯尔,你需要单子变形金刚,因为在哈斯凯尔,单子无处不在。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计算表达式的示例,该表达式会生成一个值+日志或一
典型的方法是确定工作流,该工作流捕获了您要建模的计算的本质(在您的情况下,它似乎是一个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