泛型 (F#)
F# 函式值、方法、屬性和彙總型別 (如類別、記錄和已區分的聯集) 都可以是「泛型」(Generic)。 泛型建構至少包含一個型別參數,此參數通常是由泛型建構的使用者所提供。 泛型函式和型別可讓您撰寫適用於各種型別的程式碼,而不需對每一種型別重複輸入程式碼。 由於編譯器的型別推斷和自動一般化機制通常會將程式碼隱含推斷為泛型,所以要讓程式碼在 F# 中成為泛型很簡單。
// Explicitly generic function.
let function-name<type-parameters> parameter-list =
function-body
// Explicitly generic method.
[ static ] member object-identifer.method-name<type-parameters> parameter-list [ return-type ] =
method-body
// Explicitly generic class, record, interface, structure,
// or discriminated union.
type type-name<type-parameters> type-definition
備註
在函式或型別名稱之後的角括號中,明確泛型函式或型別的宣告非常類似於非泛型函式或型別的宣告,但型別參數的規格 (和使用方式) 除外。
宣告通常為隱含泛型。 如果您並未完全指定每一個用於組成函式或型別之參數的型別,則編譯器會嘗試根據您撰寫的程式碼來推斷每一個參數、值和變數的型別。 如需詳細資訊,請參閱型別推斷 (F#)。 如果型別或函式的程式碼並未限制參數的型別,則函式或型別即為隱含泛型。 此處理序稱之為「自動一般化」(Automatic Generalization)。 自動一般化有一些限制。 舉例來說,如果 F# 編譯器無法推斷泛型建構的型別,則該編譯器所回報的錯誤會參考一種稱為「值限制」(Value Restriction) 的限制。 在這種情況下,您可能必須加入一些型別附註。 如需自動一般化和值限制的詳細資訊,以及如何變更程式碼來解決問題的詳細資訊,請參閱自動產生 (F#)。
在先前的語法中,type-parameters 是一份代表未知型別的參數清單 (以逗號分隔),而每一個參數都是以單引號開頭,並選擇性加入條件約束子句,以進一步限制哪些型別可用於該型別參數。 如需各種條件約束子句的語法以及條件約束的其他相關資訊,請參閱條件約束 (F#)。
語法中的 type-definition 與非泛型型別的型別定義相同。 這包含類別型別的建構函式參數、選擇性 as 子句、等號、記錄欄位、inherit 子句、已區分聯集的選項、let 和 do 繫結、成員定義,以及非泛型型別定義中允許的任何其他項目。
其他語法項目與非泛型函式和型別的語法項目相同。 例如,object-identifier 是一個代表包含物件本身的識別項。
屬性、欄位和建構函式不能比封入型別更加泛型。 此外,模組中的值不可以是泛型的。
隱含泛型建構
當 F# 編譯器推斷程式碼中的型別時,它會自動將任何可以成為泛型的函式自動視為泛型。 如果您明確指定型別 (如參數型別),則會防止自動一般化。
在下列程式碼範例中,makeList 是泛型的,即使它或它的參數皆未明確地宣告為泛型。
let makeList a b =
[a; b]
此函式的簽章被推斷為 'a -> 'a -> 'a list。 請注意,這個範例中的 a 和 b 被推斷為具有相同的型別。 這是因為它們一起包含在清單中,而且清單的所有項目都必須是相同的型別。
您也可以在型別附註中使用單引號語法,表示參數型別是泛型型別參數,而讓函式成為泛型的。 在下列程式碼中,function1 是泛型的,因為它的參數已用這種方式宣告為型別參數。
let function1 (x: 'a) (y: 'a) =
printfn "%A %A" x y
明確泛型建構
以角括號 (< >) 明確宣告函式的型別參數,也可以讓函式變成泛型函式。 以下的程式碼可說明這點。
let function2<'T> x y =
printfn "%A, %A" x y
使用泛型建構
當您使用泛型函式或方法時,您不一定要指定型別引數。 編譯器會使用型別推斷來推斷適當的型別引數。 如果仍然模稜兩可,您可以在角括號中提供型別引數,並以逗號分隔多個型別引數。
下列程式碼顯示如何使用先前章節中定義的函式。
// In this case, the type argument is inferred to be int.
function1 10 20
// In this case, the type argument is float.
function1 10.0 20.0
// Type arguments can be specified, but should only be specified
// if the type parameters are declared explicitly. If specified,
// they have an effect on type inference, so in this example,
// a and b are inferred to have type int.
let function3 a b =
// The compiler reports a warning:
function1<int> a b
// No warning.
function2<int> a b
注意事項 |
---|
依照名稱參考泛型型別的方法有兩種。 例如,list<int> 和 int list 就是兩種用於參考具有單一型別引數 int 之泛型型別 list 的方法。 後面的形式照慣例僅適用於內建的 F# 型別,如 list 和 option。 如果有多個型別引數,您通常會使用語法 Dictionary<int, string>,但是也可以使用語法 (int, string) Dictionary。 |
以萬用字元做為型別引數
若要指定應該由編譯器推斷型別引數,您可以使用底線、萬用字元符號 (_),而非使用具名的型別引數。 請參考下列程式碼中的示範。
let printSequence (sequence1: Collections.seq<_>) =
Seq.iter (fun elem -> printf "%s " (elem.ToString())) sequence1
泛型型別和函式的條件約束
在泛型型別或函式定義中,您只可以使用已知可用於泛型型別參數上的建構。 如此一來,才能在編譯階段啟用函式和方法呼叫的驗證。 如果您明確宣告型別參數,則可以將明確條件約束套用至泛型型別參數,以通知編譯器可以使用某些方法和函式。 不過,如果您允許 F# 編譯器推斷您的泛型參數型別,則它會為您判斷適當的條件約束。 如需詳細資訊,請參閱條件約束 (F#)。
以統計方式解析的型別參數
F# 程式中可以使用的型別參數有兩種。 第一種是前幾節中所描述的那種泛型型別參數。 這一種型別參數等同於在 Visual Basic 和 C# 等語言中使用的泛型型別參數。 另一種型別參數則專屬於 F#,稱之為「以統計方式解析的型別參數」(Statically Resolved Type Parameter)。 如需這類建構的相關資訊,請參閱以統計方式解析的型別參數 (F#)。
範例
// A generic function.
// In this example, the generic type parameter 'a makes function3 generic.
let function3 (x : 'a) (y : 'a) =
printf "%A %A" x y
// A generic record, with the type parameter in angle brackets.
type GR<'a> =
{
Field1: 'a;
Field2: 'a;
}
// A generic class.
type C<'a>(a : 'a, b : 'a) =
let z = a
let y = b
member this.GenericMethod(x : 'a) =
printfn "%A %A %A" x y z
// A generic discriminated union.
type U<'a> =
| Choice1 of 'a
| Choice2 of 'a * 'a
type Test() =
// A generic member
member this.Function1<'a>(x, y) =
printfn "%A, %A" x, y
// A generic abstract method.
abstract abstractMethod<'a, 'b> : 'a * 'b -> unit
override this.abstractMethod<'a, 'b>(x:'a, y:'b) =
printfn "%A, %A" x y