In F# discriminated unions are providing a base for the powerful pattern matching. Let's examine what happens behind the scene when someone enters the following innocent-looking code:
type Test = | Option1 of float | Option2 of int
If we take the generated assembly apart, this simple code generates a relatively large amount of code. First of all, a class (type) named Test is going to be created. What is more surprising is that five nested classes will also be defined. Of this five, two seems to be crucial as we'll see while the others seem to be more about administration.
Talking about administration, the resulting type implements a fair amount of interfaces automatically.
IEquatable adds three Equals methods: one with the Test type, but also
two with object type (one of which allows a second IEqualityComparer
parameter). Similarly IComparable and IComparable
What is more important is that one nested class that is inherited from Test will be created for each option, named Option1 and Option2. Both of these classes will contain a property named Item and not surprisingly Item will be float64 in Option1 and int32 in Option2.
Test will also contain two more boolean properties: IsOption1 and IsOption2 which return whether this refers to an instance of the respective type.
Finally Test provides two factory constructors named NewOption1 and NewOption2 which create either of the nested option instances. Its constructor in fact is not public (and doesn't do anything except create the useless parent Test instance).
An implementation in C# therefore (removing much of the automatically generated interface implementations and helper methods) would look like this. The generated IL is very similar to that of the original F# code.
public class Test
{
public class Option1 : Test
{
public Option1(double v) { item = v; }
readonly double item;
double Item { get { return item; } }
}
public class Option2 : Test
{
public Option2(int v) { item = v; }
readonly int item;
int Item { get { return item; } }
}
public sealed class Tags
{
public const int Option1 = 0;
public const int Option2 = 1;
}
Test() {}
public static Test NewObject1(float v) { return new Option1(v); }
public static Test NewObject2(int v) { return new Option2(v); }
public bool IsOption1 { get { return (this is Option1); } }
public bool IsOption2 { get { return (this is Option2); } }
public int Tag { get { return ((this is Option1) ? Tags::Option1 : Tags::Option2); } }
}
There are two further nested classes named Option1@DebugTypeProxy and Option2@DebugTypeProxy, but I'm not really sure what their functionality is. Also, there is that Tags nested class with its const definitions and the Tag property in Test whose role is also not completely clear from this IL code. It can be related to enums or maybe to do with performance
After comparing the two implementations, it is very easy to appreciate how - in probably Don Syme's favorite word - succinct F# really is (and that's even before diving into heavyweight functional programming.