Thomas
Thomas

Reputation: 12107

how can I combine a list of Result<> in F#?

with this code:

let a = [3; 4; 5; 6; 7]
let check x = if x % 2 = 0 then Ok x else Error x
let b = a |> List.map check

how can I summarize B as:

I could make a method that does this, but I can't help thinking that this has to be built in, or there has to be a clever simple way to make it happen (I have FsToolkit.ErrorHandling btw, in case it's built in there.

Upvotes: 2

Views: 981

Answers (4)

Martin Freedman
Martin Freedman

Reputation: 738

The function you need is traverseResultA in the list functions for FsToolkit.ErrorHandling. See traverseResultA where it says "This is applicative, collecting all errors. Compare the example ...with traverseResultM." (The latter is a monad which gets the first error).

#r "nuget: FsToolkit.ErrorHandling"
open FsToolkit.ErrorHandling

let a = [3; 4; 5; 6; 7]
let aOk = [4;6]
let aError = [3;5;7]
let check x = if x % 2 = 0 then Ok x else Error x
>
val a : int list = [3; 4; 5; 6; 7]
val aOk : int list = [4; 6]
val aError : int list = [3; 5; 7]
val check : x:int -> Result<int,int>


aOk |> List.traverseResultA check
>
val it : Result<int list,int list> = Ok [4; 6]

aError |> List.traverseResultA check
>
val it : Result<int list,int list> = Error [3; 5; 7]

a |> List.traverseResultA check
>
val it : Result<int list,int list> = Error [3; 5; 7]

An explicit version could be:

let traverseResultA data =
    let value = function Ok x -> x | Error y -> y 
    let filter = function Ok _ -> true | Error _ -> false
    if List.forall filter data then data |> List.map value |> Ok
    else data |> List.filter (not<<filter) |> List.map value |> Error
> 
val traverseResultA : data:Result<'a,'a> list -> Result<'a list,'a list>

See also Scott's article for explaining traverse and applicatives versus monads.

Upvotes: 1

Brian Berns
Brian Berns

Reputation: 17038

There are a lot of cool ideas in the responses here, but IMHO they are overkill for the problem as described. So just for the record, here's a very simple solution:

let a = [3; 4; 5; 6; 7]
let check x = (x % 2 = 0), x
let b =
    a
        |> List.map check
        |> List.partition fst
        |> function
            | evens, [] -> Ok evens
            | _, odds -> Error odds

Upvotes: 1

kaefer
kaefer

Reputation: 5741

You can in fact write the function yourself, once you realize that you want to transform a list of Result<'a,'b> into a single Result<'a list,'b list>.

module Result =
    let ofList arg = 
        (arg, Ok[]) ||> List.foldBack (fun t s ->
        match t, s with
        | Ok x,    Ok xs    -> Ok(x::xs)        // all Ok so far, prepend to state
        | Error e, Ok _     -> Error[e]         // first Error, discard accumulated state
        | Ok _,    Error es -> Error es         // ignore every Ok after first Error
        | Error e, Error es -> Error(e::es) )   // second or later Error, prepend to state
    // val ofList : arg:Result<'a,'b> list -> Result<'a list,'b list>

[3; 4; 5; 6; 7]
|> List.map (fun x -> if x % 2 = 0 then Ok x else Error x)
|> Result.ofList
// val it : Result<int list,int list> = Error [3; 5; 7]

Upvotes: 4

Guran
Guran

Reputation: 424

If I understand your question correctly, you are looking for a traverse or mapM function. They are not built into native fsharp, but implementations exists. I will not link to a particular library here since new libraries might pop up later.

You can read more about about it here: fsharpforfunandprofit

Upvotes: 0

Related Questions