F# Enumerations

F# Discriminated Unions are amazing. To give a common example using shapes, they simplify this C#

 
    interface IShape
    {
        double Area { get; }
    }
 
    class Circle : IShape
    {
        public double Radius { get; }
        public double Area { get; }
 
        public Circle(double radius)
        {
            Radius = radius;
            Area = Radius * Radius * Math.PI;
        }
    }
 
    class Rectangle : IShape
    {
        public double Width { get; }
        public double Height { get; }
        public double Area { get; }
 
        public Rectangle(double width, double height)
        {
            Width = width;
            Height = height;
            Area = Width * Height;
        }
    }

to this F#:

type Shape = 
  | Rectangle of Width : float * Height : float
  | Circle of Radius : float
  member this.Area = 
    match this with
    | Rectangle(width, height) -> (width * height)
    | Circle(radius) -> (radius * radius * Math.PI)

This F# Area property also illustrates something powerful gained: pattern matching on a discriminated union. While logic with pattern matching is very powerful, F# discriminated unions have another use, as regular enumerations on a finite set.

type GreekDeity = 
  | Zeus
  | Poseidon
  | Hades
  member this.Realm =
    match this with 
    | Zeus -> "Sky"
    | Poseidon -> "Ocean"
    | Hades -> "Underworld"

A F# Discriminated Union and Enumerations are similiar with some distinct differences. To rewrite GreekDeity as an enum:

type GreekDeity = 
  | Zeus = 0
  | Poseidon = 1
  | Hades = 2
 
module DeityLogic = 
  let Realm (deity: GreekDeity) =
      match deity with 
      | GreekDeity.Zeus -> "Sky"
      | GreekDeity.Poseidon -> "Ocean"
      | GreekDeity.Hades -> "Underworld"
      | _ -> ""

The realm logic had to be broken out since enumerations cannot contain members. Also, since enumerations are not completely safe like discriminated unions, the blank case has to be handled.

Superficially, this seems like a step backward with some extra steps to run through. But we’ve gained something very important: ease of use from C#.

//This only works if GreekDeity is an enum.
foreach (string name in Enum.GetNames(typeof(Logic.GreekDeity)))
{
    Console.WriteLine(name);
}
 
Console.WriteLine();
 
foreach (Logic.GreekDeity deity in Enum.GetValues(typeof(Logic.GreekDeity)).Cast<Logic.GreekDeity>())
{
    string realm = Logic.DeityLogic.Realm(deity);
    Console.WriteLine("Deity \"{0}\" - Realm \"{1}\"", deity, realm);
}
Zeus
Poseidon
Hades

Deity "Zeus" - Realm "Sky"
Deity "Poseidon" - Realm "Ocean"
Deity "Hades" - Realm "Underworld"

This is important because a common use case for F# is to be business logic for C#. An enumeration is also easier to serialize and deserialize since it’s just an integer. Example code.