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

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

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

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. 刚好有三个 Planet,由区分联合定义。
  2. 我们在地图 planets 中拥有整个集合,可以对其进行操作、迭代等。
  3. planets.[Mars] 会导致错误,因为“火星”不是 Planet.

但不利的一面是:

  1. union和map之间不一定是一对一的映射关系。需要两次提及每个行星是一个缺点。这是解决最后一点的另一种方法:
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"] 不会导致编译时错误,因为行星标识符现在是“字符串类型”。

有没有什么方法可以做到这一点,同时兼具这四个优点?

这个怎么样?

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)
        

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

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

此方法不需要反射并提供编译时类型检查。所以它与你的第二种方法几乎相同,莫妮卡。