【问题标题】:Turn list of Result into Result of list inside a computation expression?将结果列表转换为计算表达式中列表的结果?
【发布时间】:2018-11-20 04:37:19
【问题描述】:

我有一个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()

我可以在result { ... } 中使用all,但是否可以进一步集成?例如通过实现ResultBuilder.For?

【问题讨论】:

  • 它不在 FSharpCore 中,但一些扩展库实现了该功能,在 F#+ 中,例如它被称为 sequence,它适用于任何 Traversable

标签: f# computation-expression


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    这是我的解决方案,它采用结果序列(不是列表),因此验证是惰性的。

    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
    

    【讨论】:

      【解决方案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 // 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
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-15
        • 2018-10-08
        • 2018-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多