问题描述
我有一组有限的相同类型的事物,我希望以强类型的方式表示它们。我希望能够操作完整的集合并轻松提取元素。这是一种方法:
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
这种方法的优点是:
- 正好有 3 个
Planet
,由可区分联合定义。 - 我们在地图
planets
中有整个集合,可以对其进行操作、迭代等。 -
planets.[Mars]
会导致错误,因为“Mars”不是Planet
。
但不利的一面是:
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)