2.4.4.1 度量单位(UNITS OF MEASURE)
1999 年,美国国家航空航天局(NASA)的气候卫星丢失,就是因为开发团队中部分成员使用公制,而另外有人使用英制的度量单位,部分成员希望距离用米、重量用公斤为单位表示,而其他成员却提供了英寸和磅的数据。这一事件是导致 F# 添加称为度量单位这项新功能的动机之一,它能够避免这种问题。我们将在第十三章讨论度量单位,这里,我们想要使用度量单位来演示类型检查对编写 F# 代码的帮助。我们选择这个示例,是因为它很容易解释,实际上,编译时的类型检查对写任何 F# 代码都有效。
清单 2.8 是 F# Interactive 中的一小段的会话,计算测试汽车的速度是否违反了最大限速的规定。
清单 2.8 用度量单位计算速度(F# Interactive)
> let maxSpeed = 50.0<km/h> <- 最大限速 km/h
let actualSpeed = 40.0<mile/h> <- 实际速度 mph
;;
val maxSpeed : float<km/h> | [1]
val actualSpeed : float<mile/h> |
> if (actualSpeed > maxSpeed) then [2]
printfn "Speeding!";;
Error FS0001: Type mismatch.
Expecting a float<mile/h> but given afloat<km/h>. | [3]
The unit of measure 'mile/h' does not matchthe unit of measure 'km/h' |
> letmphToKmph(speed:float<mile/h>) = | [4]
speed * 1.6<km/mile>;; |
val mphToKmph : float<mile/h> ->float<km/h> |
> if (mphToKmph(actualSpeed) >maxSpeed) then [5]
printfn "Speeding!";;
Speeding!
清单 2.8 首先声明两个值(maxSpeed 和 actualSpeed)。这个声明的值是有单位的,所以,你会看到第一个值是公里每小时,第二个值是英里每小时。在类型中会保存这些信息[1],因此,两个值的类型不仅是浮点数,而是有单位的浮点数。
有了这些值以后,就可以尝试比较实际速度与最大限速[2]。在没有度量单位[系统]的语言中,这种比较会完全正确,其结果将是 false(因为 40 是小于 50),因此,驾驶员会免于处罚。但 F# 编译器报告[3],不能比较这些数字,因为类型 float<km/h> 不同于 float<mile/h>。
要解决这个问题,必须定义一个函数,将速度从实现类型转换[4]。这个函数的参数为 float<mile/h> 类型,表示速度是英里每小时,并返回表示速度的浮点数,是公里每小时。我们在条件中使用这个转换函数[5],代码就可以正确编译,并报告实际速度大于最大限速。如果我们把代码改写成独立的应用程序(不使用 F# Interactive),会报告一个有关单位的编译时错误。此外,你可以在 Visual Studio 中看到这些单位,有助于验证代码的正确性。如果看到一个应该表示速度的值,类型为 float<km^2>,你很快就能意识到有错误。注意,在 F# 中的度量单位,有一个叫零运行时的不足(there’s a zero runtime penalty;实际上,可能就是在运行时对度量单位不进行类型检查;有点绕啊),验证代码的正确性是作为类型检查的一部分在编译时完成的。
并不是所有的函数语言中都有静态类型检查,但是,对 F# 是非常重要的。在最后几节,我们讨论了对于函数语言来说非常重要的概念,可以看到,有些函数式结构与我们在平常命令式和面向对象语言中所见的类似的结构,是有差别的;有些功能我们可能还不太熟悉,但在本书的后面会详细讨论每个概念,因此,你可以学习了更多关于函数编程内容以后,再回到这里看看这个“大片”的概览。