Let's say I have a discriminated union consisting of three cases. Case A
and C
each takes a constructor to type X
and Y
respectively. I have a list consisting of different DU types and I want to filter that list down to a single DU type. Currently, I have a list consisting each of A
, B
and C
. Now, if I want to filter the DU list only to type case A
, how can I do that without having to pass the constructor to case A
? (or pass a default constructor, I don't know how to do that either)
type X = {
Id: string
Name: string
}
type Y = {
Id: int
}
type DU =
| A of a:X
| B
| C of b:Y
let extractDUTypesFromList (listDU: List<DU>) (typ: DU) : List<DU> =
listDU
|> List.filter (fun m -> m = typ)
let a = (A {Id = "1"; Name = "Test"})
let aa = (A {Id = "2"; Name = "Test 2"})
let b = B
let c = (C {Id = 1})
let listDU: List<DU> = [a; b; c; aa]
let filteredDUList: List<DU> = // this list will only contain case A
extractDUTypesFromList listDU (A _) // doesn't work
In order to filter like that we need the opposite of the DU constructor, which is an active recognizer.
Unfortunately you'll have to create them by hand, although I did a suggestion to have the F# compiler derive them automatically, this is a good example of why such suggestion matters.
// Active recognizers (ideally autogenerated)
let (|A|_|) = function | A x -> Some x | _ -> None
let (|B|_|) = function | B -> Some () | _ -> None
let (|C|_|) = function | C x -> Some x | _ -> None
let inline extractDUTypesFromList (listDU: List<DU>) (typ: DU -> Option<'t>) : List<DU> =
listDU
|> List.choose (fun x -> typ x |> Option.map (fun _ -> x))
let a = (A {Id = "1"; Name = "Test"})
let aa = (A {Id = "2"; Name = "Test 2"})
let b = B
let c = (C {Id = 1})
let listDU: List<DU> = [a; b; c; aa]
let filteredDUList: List<DU> = // this list will only contain case A
extractDUTypesFromList listDU (|A|_|)
results in
val filteredDUList : List<DU> = [A { Id = "1"
Name = "Test" }; A { Id = "2"
Name = "Test 2" }]
No need to say that you can make normal functions instead of active recognizers, since in this usage alone we're not using pattern matching at all, I mean you can name the function tryA
as suggested, instead of (|A|_|)
.