How do I extract values from Results without pattern matching?

I have the code below (not real code, I’m just experimenting).
The raw data comes into the system.
Each item of raw data is validated and placed in a Result.
The results are then split into two collections, one for valid records, the other for invalid records.
Each of these collections will be processed by a different part of the system – maybe one gets loaded to a database and the other gets manually checked by a user, for instance.

How do I get the wrapped values out of the Results in the collections without pattern matching?
I can’t ‘dot into’ the results so I can’t see their values.
I don’t want to pattern match as that could introduce ‘dummy’ records if the results were to be processed without partitioning.

I’ve found lots of examples of getting values into a Result but I can’t seem to find any for getting values out of a Result (except via pattern matching).

If I am doing this all wrong then please tell me, and give me some clues as to how to do it better.

let rawData = [
 (1, 2); (2, 4); (3, 6); (4, 8); (5, 10);
 (6, 12); (7, 14); (8, 16); (9, 18); (10, 20) ]

type validItem = { A :int; B :int }
type errorItem = { A :int; B :int; Fields :string }

let (|BadA|GoodA|) a = if a % 3 = 0 then BadA else GoodA
let (|BadB|GoodB|) b = if b % 4 = 0 then BadB else GoodB

let construct (a, b) =
 match a, b with
 | GoodA, GoodB -> Ok { A = a; B = b }
 | GoodA, BadB -> Error { A = a; B = b; Fields = "B"}
 | BadA, GoodB -> Error { A = a; B = b; Fields = "A"}
 | a, b -> Error { A = a; B = b; Fields = "AB"}

let validatedItems = rawData |> List.map construct

let isValidItem item =
 match item with
 | Ok _ -> true
 | Error _ -> false

let onlyValidItems, onlyErrorItems = validatedItems |> List.partition isValidItem

// What can I do here?

val onlyValidItems: Result<validItem,errorItem> list =
 [Ok { A = 1
 B = 2 }; Ok { A = 5
 B = 10 }; Ok { A = 7
 B = 14 }]
val onlyErrorItems: Result<validItem,errorItem> list =
 [Error { A = 2
 B = 4
 Fields = "B" }; Error { A = 3
 B = 6
 Fields = "A" }; Error { A = 4
 B = 8
 Fields = "B" };
 Error { A = 6
 B = 12
 Fields = "AB" }; Error { A = 8
 B = 16
 Fields = "B" }; Error { A = 9
 B = 18
 Fields = "A" };
 Error { A = 10
 B = 20
 Fields = "B" }]

Your isValidItem function could be renamed to isOk and made an extension of Result, similar to .isSome for options.

If you’re interested, F#+ comes with many handy functions to deal with stuff like this, for instance you could use Result.partition this way:
let onlyValidItems, onlyErrorItems = validatedItems |> Result.partition

There also stuff there like List.partitionMap which allows you to create 2 groups “on the fly”.

If you find those functions useful you could either link the library or copy the source portion you need.

Thanks for replying.

Someone told me about F#+ in the answer to another question but it looked ‘pro-level’ to me so I skirted round it.

However, I’ve had another quick look and Result.partition does the job very nicely (and I can also lose the IsValidItem function).

List.partitionMap looks like it could be a good fit for the code, as it currently is, as I’m not doing any intermediate step between the validation and the splitting, but, it uses Choices and I’ve not used them yet and can’t find a good example of usage that I can understand at the moment.

Anyway, I’ll keep looking around and see if I can get over this new ‘Choice hurdle’ – so much to learn, but interesting to do so.

Thanks again.

Ah, well, that was simpler than I thought it was going to be.

Here’s the code for the List.partitionMap version:

// This code is a replacement for the code from the ‘let construct’ in the above code.
open FSharpPlus

let construct (a, b) =
 match a, b with
 | GoodA, GoodB -> Choice1Of2 { A = a; B = b }
 | GoodA, BadB -> Choice2Of2 { A = a; B = b; Fields = "B"}
 | BadA, GoodB -> Choice2Of2 { A = a; B = b; Fields = "A"}
 | a, b -> Choice2Of2 { A = a; B = b; Fields = "AB"}

let onlyValidItems, onlyErrorItems = rawData |> List.partitionMap construct

Map and Partition done at the same time; nice.

I’m sure that Choice is way more powerful and useful than the way I’ve used it but this will do for me for now.

1 Like

Similar to Option, it would be a decent idea to use Result.map or Result.bind. We have map f inp evaluates to
match inp with Error e -> Error e | Ok x -> Ok (f x)

Thanks for your advice sashab.
I’m not sure how that would help in this situation, it seems to return an “Error e” or “Ok x” when I want it to return either “e” or “x” (unwrapped values), but I’ll try and keep it in mind for future use.