Matching on Discrimination
Is it possible that a Discriminatory Union would conform in F # based on its case rather than the content of a specific case? For example, if I wanted to filter a list by items that have a case Flag
, could it be filtered as such? I am currently forced to have three separate functions to filter what I desire. This is the approach I have so far:
type Option =
{Id : string
Arg : string}
type Argument =
| Flag of string
| Option of Option
| Unannotated of string
//This is what I'm going for, but it does not work as the "other" match case will never be matched
let LocateByCase (case:Argument) (args : Argument List) =
args
|> List.filter (fun x -> match x with
| case -> true
| _ -> false)
let LocateAllFlags args =
args
|> List.filter (fun x -> match x with
| Flag y -> true
| _ -> false)
let LocateAllOptions args =
args
|> List.filter (fun x -> match x with
| Option y -> true
| _ -> false)
let LocateAllUnannotated args =
args
|> List.filter (fun x -> match x with
| Unannotated y -> true
| _ -> false)
Am I missing an aspect of the F # language that is easier to deal with?
source to share
There is no built-in way to find out the DU value. The usual approach when faced with such a requirement is to provide the appropriate functionality for each case:
type Argument =
| Flag of string
| Option of Option
| Unannotated of string
with
static member isFlag = function Flag _ -> true | _ -> false
static member isOption = function Option _ -> true | _ -> false
static member isUnannotated = function Unannotated _ -> true | _ -> false
let LocateByCase case args = List.filter case args
let LocateAllFlags args = LocateByCase Argument.isFlag args
(needless to say, the function LocateByCase
is actually redundant, but I decided to keep it to make the answer clearer)
WARNING: DIRTY HACK BELOW
Alternatively, you can specify a case as a quote and make yourself a function that will parse that quote, derive the name of the case from it, and compare it to the given value:
open FSharp.Quotations
let isCase (case: Expr<'t -> Argument>) (value: Argument) =
match case with
| Patterns.Lambda (_, Patterns.NewUnionCase(case, _)) -> case.Name = value.GetType().Name
| _ -> false
// Usage:
isCase <@ Flag @> (Unannotated "") // returns false
isCase <@ Flag @> (Flag "") // returns true
Then use this function to filter:
let LocateByCase case args = List.filter (isCase case) args
let LocateAllFlags args = LocateByCase <@ Flag @> args
HOWEVER , this is essentially a dirty hack. Its messiness and hackiness comes from the fact that since you cannot require a specific quote at compile time, it will allow meaningless programs to be made. For example:
isCase <@ fun() -> Flag "abc" @> (Flag "xyz") // Returns true!
isCase <@ fun() -> let x = "abc" in Flag x @> (Flag "xyz") // Returns false. WTF?
// And so on...
Another possibility could happen if a future version of the compiler decides to generate quotes a little differently, and your code won't recognize them and constantly report false negatives.
I would recommend avoiding fiddling with quotes if at all possible. It may look easy on the surface, but it really is simple versus simple case .
source to share