4
votes

With DU (Discriminated Union types), how do I perform a type test pattern matching ? I have this following running code :

type IU =
|Int of int
|Unit of Unit

let x = IU.Int(3)
let y = IU.Unit(())
let z = [3.14]

let showI (v) = 
    match box v with
    | :? IU -> 
        match v with
        | Int(_) -> "an IU int"
        |_ -> "not a IU.int"
    |_ -> "not a IU.int"

But I am not happy with the inner match in the showI function. I would have preferred something like :

let showI (v) = 
    match box v with
    | :? IU.Int -> "an int"
    |_ -> "not a IU.int"

which doesn't compile (error : the type Int is not defined).

Is there an obvious syntax I missed ? Thanks.

Note : showI function accepts a variable with an unknowned type ; that is the reason for the smelly box v.

2
I think your showI will already have type IU -> string so the stuff with the match box v ... is not needed anyways - if you want a general one you need let showI (v : obj) ... but then you have to do something like | :? IU.Int as myUI -> match myUI with ... - Random Dev
there will be no way around this because the first match is a runtime type-switch and the second one will be for the structural data - Random Dev
of course you can write yourself little helpers to make this look a bit nicer (you figure it out) - but I am really wondering: why do you need this in the first place - the | :? ... matches are usually a smell that something is wrong (you don't really want runtime type dispatches) - Random Dev
... and if you really really need to you can always get the .GetType().FullName and match these strings (this will closely resemble what you wanted in the second snippet) - Random Dev
Thanks @Carsten. All your comments above are useful. I confirm that I would have liked to write a showI(v:IU) instead of showI without type annotation. Unfortunately, the variable v comes from a piece of code I do not have control of. I was hoping to find a syntax to avoid nested match but your second comment about two steps (runtime switch and structural switch) seem to deter any hope. - FZed

2 Answers

5
votes

As others have pointed out, I don't think there's any built-in language feature that lets you do this. However, you could define an active pattern that performs the type test:

let (|IsIU|_|) (candidate : obj) =
    match candidate with
    | :? IU as iu -> Some iu
    | _ -> None

This active pattern has the type obj -> IU option.

You can compose your own custom active pattern with standard patterns, like this:

let showI = function
    | IsIU (IU.Int i) -> "an IU int"
    | _ -> "not a IU.int"

In this example, the custom IsIU active pattern has been composed with a standard identifier pattern that matches on the IU.Int case.

Here's a sample FSI session showing usage with the x, y, and z values given in the OP:

> showI x;;
val it : string = "an IU int"
> showI y;;
val it : string = "not a IU.int"
> showI z;;
val it : string = "not a IU.int"
1
votes

Staying within the context of your question I believe what you are missing is that IU.Int is not a type, but a case Int of discriminated union type IU. When you write

let x = IU.Int(3)

the type of value x is IU, not IU.Int. That's why compiler barks upon your attempt to match obj to UI.Int with :? pattern.

In a broader context, it seems you try approaching F# a-la dynamic language of Javascript kind, which it is not. Exaggerating a bit, you seemingly try using functions operating upon arguments of only one type obj and hence spending substantial run-time effort on dynamic discovery of specific argument types with wide opportunities for making mistakes on the way.

Such approach misses the whole point of F# idiomatic DU use case, which is disassembling of a value that is known to be statically typed as IU by pattern match machinery to specific union case (IU.Int or IU.Unit):

let showI (v : IU) =  // explicit argument type is added to illuminate the point
    match v with
    | IU.Int(x) -> sprintf "a IU.Int(%i) value" x
    | _ -> "a IU.Unit"

So, if you by mistake try calling showI with argument that is not of type IU, compiler will catch the erroneous use of your function with argument of wrong type right away and simply will not build the executable form of your code until the mistake is corrected.

EDIT: Idiomatic use aside you may get away with a single match, indeed, with the help of when guard, like in a snippet below, although this is a nasty hack:

open Microsoft.FSharp.Reflection

let showI (v) = 
    match box v with
    | :? IU as x when (fst(FSharpValue.GetUnionFields(x, typeof<IU>))).Name.Equals("Int")
        -> "an IU.Int"  
    | _ -> "not an IU.Int"