自动泛化 (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"
值限制
编译器只会对具有显式参数的完整函数定义和简单的不可变值执行自动泛化。
这意味着,如果您尝试编译未充分约束为特定类型但也不可泛化的代码,编译器将发出错误。此问题的错误消息将这种针对值的自动泛化的限制称为“值限制”。
通常,当您希望构造为泛型但编译器没有足够的信息来泛化该构造,或者当您无意中忽略了非泛型构造中足够的类型信息时,将会发生值限制错误。解决值限制错误的方法是提供更多显式信息以便更全面地约束类型推理问题,可以采用以下方式之一来进行:
通过将显式类型批注添加到值或参数,将类型约束为非泛型。
如果问题是使用不可泛化的构造来定义泛型函数,例如函数组合或未完全应用的扩充函数参数,请尝试重新将函数编写为普通函数定义。
如果问题是太过复杂而无法泛化的表达式,请通过添加额外的未使用参数来使其成为函数。
添加显式泛型类型参数。此选项很少使用。
下面的代码示例阐释了以上的每种方案。
情况 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>