FZed
FZed

Reputation: 528

Type test pattern matching for DUs

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.

Upvotes: 4

Views: 317

Answers (2)

Mark Seemann
Mark Seemann

Reputation: 233212

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"

Upvotes: 5

Gene Belitski
Gene Belitski

Reputation: 10350

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"

Upvotes: 1

Related Questions