泛型 (F#)
F# 函数值、方法、属性和聚合类型(例如类、记录和可区分联合)可以为泛型。 泛型构造至少包含一个类型参数,该类型参数通常由泛型构造的用户提供。 可以通过泛型函数和类型来编写可用于各种类型的代码,而不必针对每个类型来重复编写代码。 利用 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#)。 如果您的类型或函数的代码未以其他方式约束参数的类型,则该函数或类型将为隐式泛型。 此过程称为“自动泛化”。 自动泛化有一些限制。 例如,如果 F# 编译器无法推断泛型构造的类型,则编译器报告错误,指出存在一个称作“值限制”的限制。 在此情况下,您可能必须添加一些类型批注。 有关自动泛化和值限制以及如何更改代码来解决问题的更多信息,请参见自动泛化 (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# 专用的,它被称为“静态解析的类型参数”。 有关这些构造的信息,请参见静态解析的类型参数 (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