类型推断

承认

此示例改编自本文关于类型推断的内容

什么是类型推断?

类型推断是一种机制,允许编译器推断出使用的类型和位置。该机制基于通常称为“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