如何组合选项和结果

Cod*_*gar 5 f#

我发现自己经常返回类型Option<Result<'a,'b>>,我的推理如下:

我使用 Option 来决定我是否对处理事件感兴趣,如果我感兴趣,那么我会返回处理它的结果。

这种模式有合适的名称吗?和更好的运营商?例如,我使用:Option.map(Result.map f)是否有用于映射嵌套函子的运算符?

Tom*_*cek 9

我认为 Gus 的现有答案回答了您关于模式名称的问题。在 Haskell 中,这是使用monad 转换器实现的,而 F#+ 库也允许您在 F# 中执行此操作。

但是,这种模式在大多数 F# 代码中并不常见。原因之一是它会导致非常复杂的代码(两种类型都变得复杂,处理代码变得非常繁琐)。

如果您发现自己Option<Result<'T>>经常使用,那么我会考虑为此定义一个新类型是否有意义,也许使用在您的域中有意义的名称,以便代码的读者可以轻松理解它。你可以使用类似的东西:

type ProcessingResult<'T> = 
  | Ignored
  | Failed of string
  | Accepted of 'T
Run Code Online (Sandbox Code Playgroud)

您将失去Optionand 的内置函数Result,但您可以实现自己需要的函数。它并不太复杂:

module Processing =
  let map f = function
    | Ignored -> Ignored
    | Failed s -> Failed s
    | Accepted v -> Accepted (f v)
Run Code Online (Sandbox Code Playgroud)

这样做的好处Processing.map是更容易阅读,我认为这是一个值得做出的权衡。


Gus*_*Gus 6

我会称它为一元堆栈。

你可以定义你自己的操作符,但如果你想映射它们,你需要像“深度映射级别 2”(map >> map)这样的东西,在这种情况下通常是这样:

Some (Result<int,string>.Ok 1) |> (Result.map >> Option.map) ((+) 1)
// val it : Result<int,string> option = Some (Ok 2)
Run Code Online (Sandbox Code Playgroud)

如果您想探索更多F#+通过 2 个不同的抽象为此提供有限的支持:

  • 组合函子:通过使用Compose类型 from FSharpPlus.Data
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)
Run Code Online (Sandbox Code Playgroud)

所以它允许你组合任意的 Functor,这是你可以映射的类型。

  • Monad 转换器:在这种特殊情况下使用以下ResultT类型FSharpPlus.Data

前面的示例可以工作,只需替换Composefor ResultT,但现在您也可以执行 monadic 操作:

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)
Run Code Online (Sandbox Code Playgroud)