将结果列表转换为计算表达式中列表的结果?

时间:2018-06-10 23:58:45

标签: f# computation-expression

我有一个Result<'T, 'E> list,我想按照这些规则变成一个Result<'T list, 'E>

  • 如果ResultError,则结果应为Error
  • 如果结果为Error,则该列表应为列表中的第一个Error
  • 如果每个结果都是OK,那么结果应该是Ok并且应保持列表顺序

所以我有一个去实现如下:

let all xs = 
  let folder = fun state next -> 
    match (state, next) with 
    | (Result.Ok ys, Result.Ok y) -> ys |> List.append [ y ] |> Result.Ok
    | (Result.Error e, _) -> Result.Error e 
    | (_, Result.Error e) -> Result.Error e 
  Seq.fold folder (Result.Ok []) xs

然而,这似乎已经在标准库中实现了。有吗?

其次,我有Result的计算表达式,如下所示:

type ResultBuilder () = 
  member this.Bind(x, f) = 
    match x with 
    | Result.Ok o -> f o
    | Result.Error e -> Result.Error e
  member this.Return(value) = Result.Ok value
  member this.ReturnFrom(value) = value

let result = new ResultBuilder()

我可以在all内使用result { ... },但可以进一步整合吗?例如,通过实施ResultBuilder.For

3 个答案:

答案 0 :(得分:6)

您有一个Result<'a, 'e> list,想要一个Result<'a list, 'e>。这听起来像https://fsharpforfunandprofit.com/posts/elevated-world-4/中描述的sequence函数(与seq无关,尽管名称听起来像)。快速检查https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/result.fs表明此功能尚未在标准FSharp.Core库中实现,因此您需要自己实现它。

顺便说一句,如果你还没有阅读过Scott Wlaschin的“高架世界”系列节目,我不建议从中间开始我链接的文章。从this article开始,因为它构建了理解“遍历”和“序列”函数所需的背景知识。然后你就会知道实现其中一个函数的一般模式。

关于你的第二个问题,你能提供一些更具体的细节吗?例如,您希望ResultBuilder.For出现什么行为? for表达式的正常行为是采用Result<'a, 'e> list(或seq或数组)并对列表或seq或数组中的每个Result<'a, 'e>运行一次内部块。如果您尝试在此处使用all函数,则Result<'a, 'e>(这是F#期望CE的.For方法生成的)和{{1}之间的类型不匹配这是您的Result<'a list, 'e>方法返回的内容。具体您希望all方法做什么?

答案 1 :(得分:3)

此功能由 FsToolkit.ErrorHandling 包提供。函数 List.sequenceResultM 将返回:

  • 一个 Result.Ok 包含所有 Ok 值的列表
  • 或者,一个只包含第一个 Result.Error 值的 Error

还有一个变体 List.sequenceResultA 返回发现的所有错误的列表。

#r "nuget: FsToolkit.ErrorHandling, 2.0.0"

open FsToolkit.ErrorHandling

let xs : Result<int, string> list =
  [
    Ok 123
    Ok 456
  ]

let xa = List.sequenceResultA xs
let xm = List.sequenceResultM xs

printfn "xa: %A" xa
printfn "xm: %A" xm

let ys =
  [
    Ok 123
    Ok 456
    Error "abc"
    Ok 789
  ]

let ya = List.sequenceResultA ys
let ym = List.sequenceResultM ys

printfn "ya: %A" ya
printfn "ym: %A" ym

let zs =
  [
    Ok 123
    Error "abc"
    Error "def"
    Ok 456
  ]

let za = List.sequenceResultA zs
let zm = List.sequenceResultM zs

printfn "za: %A" za
printfn "zm: %A" zm
xa: Ok [123; 456]
xm: Ok [123; 456]
ya: Error ["abc"]
ym: Error "abc"
za: Error ["abc"; "def"]
zm: Error "abc"

在计算表达式方面(也由 FsToolkit.ErrorHandling 提供),你可以这样做:

#r "nuget: FsToolkit.ErrorHandling, 2.0.0"

open FsToolkit.ErrorHandling

let printAll xs =
  result {
    let! xs = List.sequenceResultA xs

    for x in xs do
      printfn "%A" x
  }

答案 2 :(得分:0)

这是我的解决方案,需要一个结果序列(而不是一个列表),因此验证是懒惰的。

let takeTo<'T> predicate (source: 'T seq) =
    seq {
        use en = source.GetEnumerator()
        let mutable isDone = false

        while isDone = false && en.MoveNext() do
            yield en.Current
            isDone <- predicate en.Current
    }

let fromResults rs = 
    rs
    |> Seq.scan (fun (vs, err) i ->
        match i with
        | Ok v -> (v::vs,err)
        | Error e -> (vs, Some e)) ([], None)
    |> Seq.takeTo (fun (vs, err) -> err.IsSome)
    |> Seq.last
    |> fun (vs, err) ->
        match err with
        | None -> vs |> List.rev |> Ok
        | Some err -> Error err