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"
值限制
编译器仅在具有显式参数的完整函数定义和简单不可变值上执行自动通用化。
这意味着,如果尝试编译的代码未足够限制为特定类型,但也不是通用的,编译器会发出错误。 此问题的错误消息将值自动通用化这一 限制作为值限制。
通常,如果希望构造为泛型,但编译器没有足够的信息来通用化,或者无意中省略非泛型构造中的足够类型信息时,就会发生值限制错误。 值限制错误的解决方法是提供更明确的信息,以更完全约束类型推理问题,方法之一如下:
通过将显式类型注释添加到值或参数,将类型约束为非泛型类型。
如果问题是使用不可泛型构造来定义泛型函数,例如函数组合或未完全应用的 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>