Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean way to extract results, or aggregate errors using Result, in f#

Tags:

f#

I have a system where I collect data from a grid. It is organized in rows. Each row processed returns a Result<data, string list> object. The list in the error case is a list of errors encountered during the parsing. Each row can have multiple errors, but only one valid result.

I would like to aggregate the data to a list data list if there are no errors, or make a complete list of errors, in the string list form if there is at least one error.

The final return type is Result<data list, string list>

I have the following code:

let hasErrors =
    gridRows |> List.exists (fun r -> match r with | Ok _ -> false | Error _ -> true)

// build the layer list, or return the errors
match hasErrors with
| false -> Ok (
            gridRows
            |> List.map (fun r ->
                match r with
                | Ok layer -> layer
                | Error _  -> failwith "this should never execute"
            )
          )
| true -> Error (
                gridRows
                |> List.collect (fun r ->
                    match r with
                    | Ok _ -> []
                    | Error messages -> messages
                )
             )

But this feels very clunky and hard to read.

Using Result<>, is there a way to do:

  • if any error, collect all error elements in a list and return a result with that list
  • if no errors, collect all the ok elements in a list and return a result with that list
like image 672
Thomas Avatar asked Nov 03 '25 06:11

Thomas


1 Answers

What you want to do here is a fold, which is a way to iterate over all elements of the list while keeping some sort of state as you go. The state in this case would be the ultimate resulting value - either Ok with all the values or Error with all the errors.

The folding function would be pretty simple:

let f state item = 
    match state, item with
    | Ok prevResults, Ok res -> Ok (prevResults @ [res])
    | Ok _, Error errs -> Error errs
    | Error errs, Ok _ -> Error errs
    | Error prevErrs, Error errs -> Error (prevErrs @ errs)

This function looks at both the "result accumulated so far" (aka "state") and the current item, and for each of the four possibilities, returns the appropriate new "result accumulated so far".

And then you can just use this function to fold over the list, with the initial state being Ok []:

gridRows |> List.fold f (Ok [])
like image 53
Fyodor Soikin Avatar answered Nov 05 '25 03:11

Fyodor Soikin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!