'Turn list of Result into Result of list inside a computation expression?

I have a Result<'T, 'E> list that I would like to turn into a single Result<'T list, 'E> following these rules:

  • If any Result is an Error then the result should be an Error
  • If the result is an Error it should be the first Error in the list
  • If every result is an OK then the result should be an Ok and the list order should be maintained

So I had a go and implemented this as follows:

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

However, this seems like something that might already have been implemented in the standard library. Has it?

Second, I have a computation expression for Result like this:

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()

I can use all inside of a result { ... } but is further integration possible? For example by implementing ResultBuilder.For?



Solution 1:[1]

This functionality is provided by the FsToolkit.ErrorHandling package. The function List.sequenceResultM will return either:

  • A Result.Ok containing a list of all Ok values
  • Or, a Result.Error containing just the first Error value

There is also a variant List.sequenceResultA that returns a list of all errors found.

#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"

In terms of computation expressions (also provided by FsToolkit.ErrorHandling), you could do:

#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
  }

Solution 2:[2]

(Noting that other answers here more than suffice!)

If working specifically with Lists, this can be done using the sequence function (Result<'c, 'd> list -> Result<'c list, 'd>) exposed by the Result module of the Fsharpx.Extras library (source code).

However, for more general sequences, this can be done with Seq.sequenceResultM function provided by the FsToolkit.ErrorHandling library.

Solution 3:[3]

Here is my solution that takes a sequence (not a list) of results, so the validation is lazy.

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 JustinM