F # Use generic type as template discriminator

If there is another way to achieve what I am trying to do below, please let me know. Suppose I have the following code example

type FooBar = | Foo | Bar let foobars = [Bar;Foo;Bar] let isFoo item = match item with | Foo _ -> true | _ -> false foobars |> Seq.filter isFoo 

I want to write a version of isFoo with a general or higher order that allows me to filter my list based on all other types of discriminatory union (Bar in this case).

Something like the following, where "a could be Foo or Bar

 let is<'a> item = match item with | a _ -> true | _ -> false 

However, this attempt gives the following error:

error FS0039: pattern discriminator 'a' not defined

+7
source share
2 answers

If you just want to filter the list, the easiest option is to use function to write the standard pattern matching:

 [ Foo; Bar; Foo ] |> List.filter (function Foo -> true | _ -> false) 

If you want to write a more complex general function that checks the case and then does something else, then the simplest option (which will work in general) is to use a predicate that returns true or false :

 let is cond item = if cond item then true else false // You can create a predicate using `function` syntax is (function Foo -> true | _ -> false) <argument> 

In your specific example, you have a discriminatory union in which none of the cases has any parameters. This is probably an unrealistic simplification, but if you only care about discriminated associations without parameters, then you can simply use cases as values ​​and compare them:

 let is case item = if case = item then true else false // You can just pass it 'Foo' as the first parameter to // `is` and use partial function application [ Foo; Bar; Foo ] |> List.filter (is Foo) // In fact, you can use the built-in equality test operator [ Foo; Bar; Foo ] |> List.filter ((=) Foo) 

This last method will not work if you have a more complex discriminatory union in which some cases have parameters, so this is probably not very useful. For example, if you have a list of parameter values:

 let opts = [ Some(42); None; Some(32) ] opts |> List.filter (is Some) // ERROR - because here you give 'is' a constructor // 'Some' instead of a value that can be compared. 

You can do various tricks using Reflection (to check for cases with the given name), and you can also use F # quotes to get a more pleasant and safe syntax, but I don’t think it is worth it because using pattern matching with Using function gives you a very clear code.

EDIT - Just out of curiosity, a solution that uses reflection (and it is slow and not safe, and no one should actually use it in practice unless you really know what you are doing) for example:

 open Microsoft.FSharp.Reflection open Microsoft.FSharp.Quotations let is (q:Expr) value = match q with | Patterns.Lambda(_, Patterns.NewUnionCase(case, _)) | Patterns.NewUnionCase(case, _) -> let actualCase, _ = FSharpValue.GetUnionFields(value, value.GetType()) actualCase = case | _ -> failwith "Wrong argument" 

It uses quotes to identify the case of the union, so you can write something like this:

 type Case = Foo of int | Bar of string | Zoo [ Foo 42; Zoo; Bar "hi"; Foo 32; Zoo ] |> List.filter (is <@ Foo @>) 
+5
source

As long as the union cases accept the same set of parameters, you can pass the constructor as an argument and restore the DU for comparison.

This looks more attractive if the parameters Foo and Bar have parameters:

 type FooBar = Foo of int | Bar of int let is constr item = match item with | Foo x when item = constr x -> true | Bar x when item = constr x -> true | _ -> false 

In your example, the constructors have no arguments. This way you can write is a simpler way:

 type FooBar = Foo | Bar let is constr item = item = constr [Bar; Foo; Bar] |> Seq.filter (is Foo) 
+3
source

All Articles