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?

+3


source to share


1 answer


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 .

+4


source







All Articles