'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
Resultis anErrorthen the result should be anError - If the result is an
Errorit should be the firstErrorin the list - If every result is an
OKthen the result should be anOkand 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.Okcontaining a list of allOkvalues - Or, a
Result.Errorcontaining just the firstErrorvalue
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 |
