型別推斷

承認

此示例改編自本文關於型別推斷的內容

什麼是型別推斷?

型別推斷是一種機制,允許編譯器推斷出使用的型別和位置。該機制基於通常稱為“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