F#:如何用强类型表示有限集合?

问题描述

我有一组有限的相同类型的事物,我希望以强类型的方式表示它们。我希望能够操作完整的集合并轻松提取元素。这是一种方法

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

这种方法的优点是:

  1. 正好有 3 个 Planet,由可区分联合定义。
  2. 我们在地图 planets 中有整个集合,可以对其进行操作、迭代等。
  3. planets.[Mars] 会导致错误,因为“Mars”不是 Planet

但不利的一面是:

  1. 联合和映射之间不一定存在一对一映射。需要两次提及每个行星是一个缺点。这是解决最后一点的另一种方法
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

所以现在每个行星只在一个地方被提及,但是 planets.["Mars"] 不会导致编译时错误,因为行星标识符现在是“字符串类型”。

有没有什么方法可以同时具备四个优点?

解决方法

另一种选择是使用 Planet 类型作为 PlanetInfo 类型中的 Name 成员,并使用来自列表的转换来初始化 Map:

module Planets =
    type Planet = 
    | Mercury
    | Venus
    | Earth

    type PlanetInfo = { Name: Planet; Diameter: float}

    let planets : PlanetInfo list = 
        [
            {Name = Mercury; Diameter = 100.}
            {Name = Venus; Diameter = 200.}
            {Name = Earth; Diameter = 300.}
        ]

    let planetsmap = planets |> List.map (fun pi -> pi.Name,pi) |> Map.ofList 

    
    planetsmap.[Mercury].Diameter

这种方法不需要反射并提供编译时类型检查。所以这和你的第二种方法几乎一样,莫妮卡。

,

这个怎么样?

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)