5.4.3.2 F# 中的类型推断
在 F# 中,通常可以写大段的代码,而无需显式指定任何类型,因为类型推理机制更复杂。创建值,我们使用 let 关键字,但实际上,我们还没有见过在使用 let 值绑定中,需要显式指定类型的任何示例。清单 5.13 是一些我们可能会想到的示例。
清单 5.13 基本值的类型推断 (F#)
let num = 123
let tup = (123, "Hello world")
let opt = Some(10)
let input = printfn"Calculating..."
if (num = 0) then None
else Some(num.ToString())
第一种情况声明了基本类型 int 值;第二种情况,我们使用元组的值构造函数,因此,会得到 int * string 类型的值;在清单 5.13 中接下来的两个绑定,创建了选项类型的值,更具体地说,是 int option 与 string option。
这些绑定,只有最后一个示例特别有趣或出人意料。我们已经知道,F# 中的一切都是表达式,因此,类型推断必须处理任何 F# 表达式(即,任何 F# 代码,因为一切都是表达式)。在这里,代码首先输出一些内容到屏幕,然后,使用条件表达式返回选项类型。注意,使用 F# 的轻量级语法,空格[注意,必须是空格]是有意义的,因此,if 表达式的起始位置与 printfn 调用要对齐。
F# 编译器知道,指定给 input 的值是从条件分支中返回的。真分支的类型是泛型 'a option 类型(因为我们将返回 None),但还不知道实例化后是什么类型;这要从假分支来推断,这里返回包含字符串的 Some 值。
我们提到过,F# 的类型推断更复杂,因此,现在要看一些稍微复杂的示例:
> let (n : option<System.Random>)= None;; | [1]
val n : System.Random option |
> let n = (None :option<System.Random>);; |
val n : System.Random option |
> let n = None;;
val n : 'a option [2]
前面两个示例[1]显示了添加类型批注(type annotation)的两种不同方法。通常,可以把类型批注放在任何 F# 代码块(如果需要)附近;下一个示例更重要,F# 不能推断出值的完整类型,知道我们要创建泛型选项类型值,但并不知道我们想要使用泛型类型的参数。有意义的是,这并不会导致错误,而是创建了泛型值(generic value)。C# 中没有与此相当的构造,它是只有部分指定类型的值。F# 使用类型参数(可以看到,F# 自动使用以 a 开头的字母命名类型参数),而不是具体类型(如 int 或 System.Object),完整指定类型则要等到后面使用值的时候。例如,我们可以比较包含了不同选项值类型的值,而不会出错:
> Some("testing...") = n;;
val it : bool = false
> Some(123) = n;;
val it : bool = false
我们已经知道如何声明和创建泛型值,那么,就应该讨论一下如何使用泛型来写函数了!有关泛型函数的详细讨论以后再说,现在,用一个示例子来吊吊你的胃口。
----------
2 这不是唯一可能有效的类型,例如,我们可能想用 Option<long>。在 C# 3.0 中,使用泛型方法进行类型推断的规则太多且复杂,但是,在编译器能够执行推断的情况下,通常会得到所需的结果。