型別推斷
承認
此示例改編自本文關於型別推斷的內容
什麼是型別推斷?
型別推斷是一種機制,允許編譯器推斷出使用的型別和位置。該機制基於通常稱為“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