自動產生 (F#)
F# 會使用型別推斷來評估函式和運算式的型別。本主題描述 F# 如何自動一般化函式的引數和型別,讓函式在可能的情況下與多個型別搭配運作。
自動一般化
F# 編譯器在函式上執行型別推斷時,會判斷指定的參數是否可以為泛型。編譯器會檢查每個參數,並判斷函式是否與該參數的特定型別相依。如果沒有,就會將型別推斷為泛型。
在下列程式碼範例中,將示範編譯器推斷為泛型的函式。
let max a b = if a > b then a else b
型別經推斷為 'a -> 'a -> 'a。
此型別表示這個函式接受兩個有相同未知型別的引數,並傳回相同型別的值。先前函式可以是泛型的原因之一是大於運算子 (>) 本身為泛型。大於運算子有簽章 'a -> 'a -> bool。並非所有運算子都是泛型,而且如果函式中的程式碼一起使用參數型別與非泛型函式或運算子,該參數型別便無法一般化。
因為 max 是泛型,所以它可以搭配 int、float 等型別使用,如下列範例所示。
let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3
不過,兩個引數的型別必須相同。簽章是 'a -> 'a -> 'a,而不是 'a -> 'b -> 'a。因此,下列程式碼會因型別不符而產生錯誤。
// Error: type mismatch.
let biggestIntFloat = max 2.0 3
max 函式也可以使用支援大於運算子的任何型別,因此也可以在字串上使用它,如下列程式碼所示。
let testString = max "cab" "cat"
值限制
只有當函式定義是具有明確引數的完整函式定義,而且值也是簡單不可變的值時,編譯器才會執行自動一般化。
這表示如果嘗試編譯的程式碼未充分限制為特定型別時,編譯器會發出錯誤,而且也不可一般化。此問題的錯誤訊息將這個值的自動一般化限制稱為「值限制」(Value Restriction)。
當您要建構為泛型但編譯器沒有足夠資訊來一般化時,或當您無意中省略非泛型建構中的必要型別資訊時,通常會發生值限制錯誤。若要解決值限制錯誤,可以透過下列其中一種方式,提供更明確的資訊以更完整地限制型別推斷問題:
透過將明確型別附註加入至值或參數,將型別限制為非泛型。
如果問題是使用不可一般化的建構定義泛型函式,例如函式複合或不完全套用的局部調用函式引數,請嘗試將函式重寫為一般函式定義。
如果問題是運算式太複雜而無法一般化,請額外加入未使用的參數,讓它成為函式。
加入明確泛型型別參數。這個選項並不常使用。
下列程式碼範例將示範上述每個案例。
案例 1:運算式太複雜。在這個範例中,counter 清單是要做為 int option ref,但是未定義為簡單不可變值。
let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None
案例 2:使用不可一般化的建構定義泛型函式。在這個範例中,建構是不可一般化的,因為它涉及函式引數的部分應用程式。
let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj
案例 3:加入額外未使用的參數。因為這個運算式有點複雜不適合進行一般化,所以編譯器會發出值限制錯誤。
let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []
案例 4:加入型別參數。
let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)
在上述的狀況中,值就是型別函式,可能會用來建立許多不同的型別值,例如,如下所示:
let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>