14

我有一个Result<'T, 'E> list我想Result<'T list, 'E>按照以下规则变成一个单一的:

  • 如果有ResultError那么结果应该是Error
  • 如果结果是 anError它应该是Error列表中的第一个
  • 如果每个结果都是 anOK那么结果应该是 anOk并且应该保持列表顺序

所以我试了一下,实现了如下:

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在 a 内部使用,result { ... }但可以进一步集成吗?例如通过实施ResultBuilder.For?

4

4 回答 4

10

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

顺便说一句,如果您还没有阅读 Scott Wlaschin 的“Elevated World”系列,我不建议您从我链接的文章开始。请从本文开始,因为它构建了您需要了解“遍历”和“序列”功能做什么的背景知识。然后你就会知道实现这些功能之一的一般模式。

至于你的第二个问题,你能否提供更多细节?例如,你想要什么行为ResultBuilder.For?表达式预期的正常行为for是获取一个(或序列或数组)并为列表或序列或数组中的Result<'a, 'e> list每个运行内部块一次。Result<'a, 'e>如果您尝试在all此处使用您的函数,您将在Result<'a, 'e>(这是 F# 期望 CE 的.For方法产生的内容)和Result<'a list, 'e>您的all方法返回的内容之间存在类型不匹配。您希望您的ResultBuilder.For方法具体做什么?

于 2018-06-11T00:51:56.330 回答
5

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

  • AResult.Ok包含所有Ok值的列表
  • 或者, aResult.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 // Ok [123; 456]
let xm = List.sequenceResultM xs // Ok [123; 456]

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

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

let ya = List.sequenceResultA ys // Error ["abc"]
let ym = List.sequenceResultM ys // Error "abc"

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

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

let za = List.sequenceResultA zs // Error ["abc"; "def"]
let zm = List.sequenceResultM zs // Error "abc"

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
  }
于 2021-02-10T14:49:19.970 回答
1

(注意这里的其他答案绰绰有余!)

如果专门使用Lists,这可以使用库模块(代码)公开的sequence函数 ( ) 来完成。Result<'c, 'd> list -> Result<'c list, 'd>ResultFsharpx.Extras

然而,对于更一般的序列,这可以通过库Seq.sequenceResultM提供的函数来完成。FsToolkit.ErrorHandling

于 2022-02-11T14:20:09.777 回答
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
于 2020-10-06T19:57:23.123 回答