【发布时间】:2020-10-02 19:18:36
【问题描述】:
我发现自己经常返回类型Option<Result<'a,'b>>,我的理由如下:
我使用 Option 来决定我是否有兴趣处理某个事件,如果有兴趣,则返回处理结果。
这种模式有合适的名称吗?以及更好的运营商?例如,我使用:Option.map(Result.map f) 是否有映射嵌套函子的运算符?
【问题讨论】:
标签: f#
我发现自己经常返回类型Option<Result<'a,'b>>,我的理由如下:
我使用 Option 来决定我是否有兴趣处理某个事件,如果有兴趣,则返回处理结果。
这种模式有合适的名称吗?以及更好的运营商?例如,我使用:Option.map(Result.map f) 是否有映射嵌套函子的运算符?
【问题讨论】:
标签: f#
我认为 Gus 的现有答案回答了您关于模式名称的问题。在 Haskell 中,这是使用 monad 转换器 实现的,而 F#+ 库也允许您在 F# 中执行此操作。
但是,这种模式在大多数 F# 代码中并不常见。一个原因是它会导致代码非常复杂(两种类型都变得复杂,处理代码也变得非常麻烦)。
如果您发现自己经常使用Option<Result<'T>>,那么我会考虑为此定义一个新类型是否有意义,也许使用在您的域中有意义的名称,以便代码的读者可以轻松理解它.你可以使用类似的东西:
type ProcessingResult<'T> =
| Ignored
| Failed of string
| Accepted of 'T
您将失去Option 和Result 的内置函数,但您可以自己实现您需要的函数。也不算太复杂:
module Processing =
let map f = function
| Ignored -> Ignored
| Failed s -> Failed s
| Accepted v -> Accepted (f v)
这样做的好处是Processing.map 更容易阅读,我认为这是值得权衡的。
【讨论】:
Option<Result<'a,'b>,因为它的构图更好;我总是拒绝创建 DU 的想法,因为那时我必须处理中间 fns 中的分支,但如果我重新实现 map,例如,那将不是问题并且会更具体,谢谢你的输入,我也会尝试这种方法,看看结果如何。
我称之为一元堆栈。
您可以定义自己的运算符,但如果要映射它们,则需要类似“深度映射级别 2”之类的东西,通常为 (map >> map),因此,在这种情况下:
Some (Result<int,string>.Ok 1) |> (Result.map >> Option.map) ((+) 1)
// val it : Result<int,string> option = Some (Ok 2)
如果您想探索更多,F#+ 通过 2 个不同的抽象对此提供了有限的支持:
FSharpPlus.Data 的Compose 类型:let v1 = Compose (Some (Result<int,string>.Ok 1))
let v2 = v1 |> map ((+) 1)
// .. more operations, then when you're finished ..
let v3 = Compose.run v2
// val v1 : Compose<Result<int,string> option> = Compose (Some (Ok 1))
// val v2 : Compose<Result<int,string> option> = Compose (Some (Ok 2))
// val v3 : Result<int,string> option = Some (Ok 2)
因此,它允许您组合任意 Functor,这些类型是您可以映射的类型。
FSharpPlus.Data 的ResultT 类型:前面的例子可以工作,只需将Compose 替换为ResultT,但现在你也可以进行一元操作:
let x = monad' {
let! v1 = ResultT (Some (Result<int,string>.Ok 1))
let! v2 = ResultT (Some (Result<int,string>.Ok 1))
return v1 + v2 }
ResultT.run x
// val x : ResultT<Result<int,string> option> = ResultT (Some (Ok 2))
// val it : Result<int,string> option = Some (Ok 2)
【讨论】: