I have a Result<'T, 'E> list
that I would like to turn into a single Result<'T list, 'E>
following these rules:
Result
is an Error
then the result should be an Error
Error
it should be the first Error
in the listOK
then the result should be an Ok
and the list order should be maintainedSo 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
?
This functionality is provided by the FsToolkit.ErrorHandling
package. The function List.sequenceResultM
will return either:
Result.Ok
containing a list of all Ok
valuesResult.Error
containing just the first Error
valueThere 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
}
(Noting that other answers here more than suffice!)
If working specifically with List
s, 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.
You have a Result<'a, 'e> list
and want a Result<'a list, 'e>
. That sounds like the sequence
function described in https://fsharpforfunandprofit.com/posts/elevated-world-4/ (which has nothing to do with seq
s, despite what the name sounds like). A quick check of https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/result.fs suggests that this function hasn't been implemented in the standard FSharp.Core library, so you'll need to implement it yourself.
BTW, if you haven't already read Scott Wlaschin's "Elevated World" series, I don't recommend starting in the middle with the article I linked. Start with this article instead, as it builds the background knowledge you need to understand what the "traverse" and "sequence" functions do. Then you'll know the general pattern for implementing one of those functions.
As for your second question, could you provide a few more specifics? For example, what behavior do you want out of ResultBuilder.For
? The normal behavior expected of a for
expression would be to take a Result<'a, 'e> list
(or seq or array) and run the inner block once for every Result<'a, 'e>
that's in the list or seq or array. If you tried to use your all
function here, you'd have a type mismatch between Result<'a, 'e>
(which is what F# would expect a CE's .For
method to produce) and Result<'a list, 'e>
which is what your all
method is returning. What specifically do you want your ResultBuilder.For
method to do?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With