F# 會使用類型推斷來評估函式和表示式的類型。 本主題描述 F# 如何自動將自變數和函式類型一般化,以便盡可能使用多個類型。
自動一般化
F# 編譯程式在函式上執行型別推斷時,會判斷指定的參數是否為泛型。 編譯程式會檢查每個參數,並判斷函式是否相依於該參數的特定類型。 如果沒有,則會推斷類型為泛型。
下列程式代碼範例說明編譯程式推斷為泛型的函式。
let max a b = if a > b then a else b
類型推斷為 'a -> 'a -> 'a。
此類型表示這是接受相同未知型別之兩個自變數的函式,並傳回該相同類型的值。 上一個函式可以是泛型的其中一個原因是大於運算符 (>) 本身是泛型。 大於 運算子具有簽章 'a -> 'a -> bool。 並非所有運算子都是泛型運算元,而且如果函式中的程式代碼使用參數類型與非泛型函式或運算符,就無法將該參數類型一般化。
因為 max 是泛型,所以可以搭配 、 intfloat等類型使用,如下列範例所示。
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"
值限制
編譯程式只會在具有明確自變數的完整函式定義和簡單不可變值上執行自動一般化。
這表示,如果您嘗試編譯的程式代碼不夠限制為特定類型,但也無法一般化,編譯程式就會發出錯誤。 此問題的錯誤訊息會將值自動一般化的限制視為 值限制。
一般而言,當您想要建構為泛型,但編譯程式沒有足夠的資訊將它一般化,或當您不小心省略非泛型建構中的足夠類型資訊時,就會發生值限制錯誤。 值限制錯誤的解決方案是透過下列其中一種方式,提供更明確的資訊,以更完整地限制類型推斷問題:
藉由將明確的類型批註新增至值或參數,將類型限製為非泛型。
如果問題是使用非一般建構來定義泛型函式,例如函式組合或未完全套用的 curried 函式自變數,請嘗試將函式重寫為一般函式定義。
如果問題是太複雜而無法一般化的表達式,請藉由新增額外的未使用參數,將其納入函式。
新增明確的泛型類型參數。 這個選項很少使用。
下列程式代碼範例說明上述每個案例。
案例 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>