Search code examples
collectionsf#strong-typing

F#: How to represent a finite collection with strong typing?


I have a finite set of things all of the same type, and I wish to represent them in a strongly-typed way. I'd like to be able to manipulate the complete set and easily extract the elements. Here is one way:

type Planet = Mercury | Venus | Earth
type PlanetInfo = { Diameter: float }
let planets =
    Map [ Mercury, { Diameter = 100. }
          Venus, { Diameter = 200. }
          Earth, { Diameter = 300. } ]
let venusDiameter = planets.[Venus].Diameter

The good points about this method are:

  1. There are exactly three Planets, as defined by the discriminated union.
  2. We have the whole set in the map planets, which can be manipulated, iterated etc..
  3. planets.[Mars] would cause an error, because "Mars" is not a Planet.

But on the downside:

  1. There is not necessarily a one-to-one mapping between the union and the map. The need to mention each planet twice is a shortcoming. Here is another method which addresses the last point:
type Planet = { Name: string; Diameter: float }
let planets =
    [ { Name = "Mercury"; Diameter = 100. }
      { Name = "Venus"; Diameter = 200. }
      { Name = "Earth"; Diameter = 300. } ]
    |> List.map (fun e -> e.Name, e)
    |> Map
let venusDiameter = planets.["Venus"].Diameter

So now each planet is mentioned in only one place, but planets.["Mars"] fails to cause a compile-time error because the planet identifiers are now "stringly typed".

Is there some way of doing this which has all four good points?


Solution

  • How about this?

    type Planet =
        |Mercury
        |Venus
        |Earth
        member this.Diameter =
            match this with
            |Mercury -> 100.
            |Venus -> 200.
            |Earth -> 300.
    
    open Microsoft.FSharp.Reflection
    let planets =
        FSharpType.GetUnionCases(typeof<Planet>)
        |> Array.map (fun case -> FSharpValue.MakeUnion(case, [||]) :?> Planet)