自动泛化

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"

值限制

编译器仅对完整的函数定义(具有显式参数)和简单的不可变值执行自动泛化。

也就是说,如果你尝试编译的代码未被充分约束为特定类型,但也不可泛化,编译器会发出错误消息。 此问题的错误消息将对值的自动泛化的这种限制称为“值限制”。

通常,如果你希望构造是泛型的,但编译器没有足够的信息来将它泛化(或者如果你在非泛型构造中无意地遗漏了足够的类型信息),则会发生值限制错误。 值限制错误的解决方案是提供更多显式信息,以便通过下面的一种方式来更充分地约束类型推理问题:

  • 向值或参数添加显式类型注释,将类型约束为非泛型类型。

  • 如果问题是使用不可泛化的构造来定义泛型函数(例如函数组合或未完全应用的扩充函数参数),请尝试将函数重写为普通函数定义。

  • 如果问题是表达式太复杂而无法泛化,可额外添加一个未使用的参数,使表达式成为函数。

  • 添加显式泛型类型参数。 这种方法不常用。

以下代码示例演示上述各场景。

情况 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>

另请参阅