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
这种方法的优点是:
- 刚好有三个
Planet
,由区分联合定义。
- 我们在地图
planets
中拥有整个集合,可以对其进行操作、迭代等。
planets.[Mars]
会导致错误,因为“火星”不是 Planet
.
但不利的一面是:
- 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
此方法不需要反射并提供编译时类型检查。所以它与你的第二种方法几乎相同,莫妮卡。
我有一组有限的所有相同类型的东西,我希望以强类型的方式表示它们。我希望能够操纵完整的集合并轻松提取元素。这是一种方法:
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
这种方法的优点是:
- 刚好有三个
Planet
,由区分联合定义。 - 我们在地图
planets
中拥有整个集合,可以对其进行操作、迭代等。 planets.[Mars]
会导致错误,因为“火星”不是Planet
.
但不利的一面是:
- 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
此方法不需要反射并提供编译时类型检查。所以它与你的第二种方法几乎相同,莫妮卡。