15

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?

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • It's not in FSharpCore, but some extension libraries have that function implemented, in [F#+](https://github.com/fsprojects/FSharpPlus) for instance it's called `sequence` and it works for any [Traversable](http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Traversable.html). – Gus Jun 13 '18 at 16:20

4 Answers4

10

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 seqs, 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?

rmunn
  • 34,942
  • 10
  • 74
  • 105
  • sorry for the late reply. What's the corresponding concept of sequence in Haskell? I read a bit of Haskell enough to understand that bind is monad, map is functor and apply is applicative functor. But couldn't connect sequence to what in haskell yet. TIA! – Tri Apr 02 '23 at 06:31
7

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
  }
sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
1

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

RMills330
  • 319
  • 1
  • 12
0

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
JustinM
  • 913
  • 9
  • 24