类型推断
承认
此示例改编自本文关于类型推断的内容
什么是类型推断?
类型推断是一种机制,允许编译器推断出使用的类型和位置。该机制基于通常称为“Hindley-Milner”或 HM
的算法。请参阅下面的一些规则,以确定简单和函数值的类型:
- 看看文字
- 查看与某些事物相互作用的函数和其他值
- 查看任何显式类型约束
- 如果在任何地方都没有约束,则自动推广到泛型类型
看看文字
编译器可以通过查看文字来推断类型。如果文字是一个 int 并且你要向它添加 x
,那么 x
也必须是一个 int。但是如果文字是一个浮点数而你正在为它添加 x
,那么 x
也必须是一个浮点数。
这里有些例子:
let inferInt x = x + 1
let inferFloat x = x + 1.0
let inferDecimal x = x + 1m // m suffix means decimal
let inferSByte x = x + 1y // y suffix means signed byte
let inferChar x = x + 'a' // a char
let inferString x = x + "my string"
查看它与之交互的函数和其他值
如果在任何地方都没有文字,编译器会尝试通过分析它们与之交互的函数和其他值来计算出类型。
let inferInt x = x + 1
let inferIndirectInt x = inferInt x //deduce that x is an int
let inferFloat x = x + 1.0
let inferIndirectFloat x = inferFloat x //deduce that x is a float
let x = 1
let y = x //deduce that y is also an int
查看任何显式类型约束或注释
如果指定了任何显式类型约束或注释,则编译器将使用它们。
let inferInt2 (x:int) = x // Take int as parameter
let inferIndirectInt2 x = inferInt2 x // Deduce from previous that x is int
let inferFloat2 (x:float) = x // Take float as parameter
let inferIndirectFloat2 x = inferFloat2 x // Deduce from previous that x is float
自动泛化
如果在所有这些之后,没有找到约束,编译器只会使类型通用。
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
类型推断可能出错的事情
类型推断并不完美,唉。有时编译器不知道该怎么做。再一次,了解正在发生的事情将真正帮助你保持冷静,而不是想要杀死编译器。以下是类型错误的一些主要原因:
- 声明不按规定
- 信息不足
- 重载方法
声明不按规定
一个基本规则是必须在使用之前声明函数。
此代码失败:
let square2 x = square x // fails: square not defined
let square x = x * x
但这没关系:
let square x = x * x
let square2 x = square x // square already defined earlier
递归或同时声明
对于必须相互引用的递归函数或定义,出现无序问题的变体。在这种情况下,任何数量的重新排序都无济于事 - 我们需要使用其他关键字来帮助编译器。
编译函数时,函数标识符不可用于正文。因此,如果你定义一个简单的递归函数,你将收到编译器错误。修复是将 rec
关键字添加为函数定义的一部分。例如:
// the compiler does not know what "fib" means
let fib n =
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
// error FS0039: The value or constructor 'fib' is not defined
这是添加了 rec fib
的固定版本,表示它是递归的:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
信息不足
有时,编译器没有足够的信息来确定类型。在以下示例中,编译器不知道 Length 方法应该处理什么类型。但它也不能使它通用,所以它抱怨。
let stringLength s = s.Length
// error FS0072: Lookup on object of indeterminate type
// based on information prior to this program point.
// A type annotation may be needed ...
可以使用显式注释修复这些类型的错误。
let stringLength (s:string) = s.Length
重载方法
在 .NET 中调用外部类或方法时,通常会因重载而出错。
在许多情况下,例如下面的 concat 示例,你必须显式地注释外部函数的参数,以便编译器知道要调用哪个重载方法。
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
有时重载的方法具有不同的参数名称,在这种情况下,你还可以通过命名参数为编译器提供线索。以下是 StreamReader 构造函数的示例。
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works