在F# 中,我们能声明的大多数类型,都是不可变的;如果我们不显式提供实现IComparable <T> 接口,并重写 Equals 方法,F# 编译器会自动实现,它是通过比较结构相等(structural equality)实现的。对F# 的类,还不能自动完成,只对简单的函数类型,比如,记录、差别联合和元组,不必要显式声明。
使用这种比较类型的值,如果它们是相等的简单类型,比如,整数或字符串,或者是相同值的组合,使用递归地结构相等,被认为是相等的。清单11.9 演示了记录的结构相等,包含元组和基本值。
清单11.9 用结构相等比较记录(F# Interactive)
> type WeatherItem =
{Temperature : int * int;
Text : string }
letwinter1 = { Temperature = -10, -2; Text = "Winter" } | 创建记录
letwinter2 = { Temperature = -10, -2; Text = "Winter" };; | 包含相同值
(...)
> System.Object.ReferenceEquals(winter1,winter2);; <-- [1] 值表示成不同的实例
val it : bool = false
> winter1.Equals(winter2);; |
val it : bool = true | [2] ...
| 但被认为相等
> winter1 = winter2;; |
val it : bool = true |
首先,我们声明了F# 记录类型,包含两个字段;第一个字段类型是两个整数的元组,第二个字段是字符串。我们创建了这个记录类型的两个值,每个对应字段的值完全相同。
我们可以看到,确实有两个实例:引用相等的测试[1],返回false。如果我们使用重写的Equals 方法,或者标准的F# 运算符测试相等[2],运行时将使用结构相等,报告这两个值相等。首先,两个元组值比较结构相等,然后,比较两个字符串。
正如我们前面所说,这种方法适用于记录、元组、差别联合和数组;因为不可变的F# 列表声明为差别联合,所以,可以同等对待。我们将在写单元测试期望时,使用此功能,但首先,我们要看一下另外的功能,自动生成函数。我们已经知道使用结构相等,可以测试值是否相等,而F# 还提供了结构比较(structural comparisons),可以排序:
> let summer = { Temperature = 10, 20; Text= "Summer" };;
(...)
> summer = winter1;;
val it : bool = false
> summer > winter1;;
val it : bool = true
这段代码用清单11.9 中声明的记录类型,创建了一个新的值,并与前面清单中的值进行比较。第一个结果并不奇怪:这两个值是不同的;第二个结果值得解释一下。为什么会认为summer 值比winter1 大呢?原因是,F# 编译器还为WeatherItem 类型生成了默认的比较[运算]。比较按照它们声明的顺序,使用字段的值:例如,元组值(10,0) 比(9,100) 大。这个默认行为是有益的,特别是,如果在设计自己的类型时,把它考虑进去。但本章的其余部分,我们侧重于结构相等。