如果我创建下面的添加功能
let add x y =
x + y
然后单独运行下面的行,我会得到一个错误,因为 F# 默认情况下假设 x 和 y 应该是整数。
add 5.4 3.2
也就是说,如果我一起运行它们, add 工作得很好,因为它现在将其视为一个获取两个浮点数的函数(这意味着编译器会提前进行类型推断)。
这就引出了一个问题,为什么相同的前瞻过程不允许 F# 知道 toHackerTalk 正在接收字符串?即使我一起运行以下几行,它也会给我一个错误,因为短语的类型是不确定的。
let toHackerTalk phrase =
phrase.Replace('t', '7').Replace('o', '0')
toHackerTalk "this be strange"
我认为这就是简单的看法。 在第一种情况下,编译器知道
+
(它在 F# 核心中定义),并且如果它随后看到 int
,它只是选择默认类型 float
(可能在规范中的某处定义)用法,选择不同版本的 +
。
另一方面,编译器没有足够的信息来选择具有
Replace
成员的类型,因此无法编译该函数
规范在第 5.2.3 节中提到了这种行为:
使用重载运算符不会产生通用代码,除非定义被标记为内联。例如,函数
let f x = x + x
导致函数 f 只能用于添加一种类型的值,例如 int 或 float。具体类型由后面的约束决定。
一般来说,每当编译器推断出由于某种原因而不能真正通用的“通用”定义时,就会出现此问题。 在使用像
(+)
这样的运算符的情况下,存在一个静态成员约束,它会阻止 add
成为泛型,除非它也被声明为 inline
。
出现此类限制的另一种情况是:
let d = System.Collections.Generic.Dictionary()
d.[0] <- "test"
如果您一起评估这些行,
d
将是Dictionary<int,string>
,但如果您单独评估它们,则在评估第二行时会出现错误,因为d
将被推断为Dictionary<obj,obj>
。 根据其定义, d
可以是任何 Dictionary<'a,'b>
和 'a
的 'b
,但它不是通用定义,因为不存在通用值(嗯,几乎;参见 [<GeneralizableValue>]
的使用)
例外)。
根据很久以前对规范的阅读,我的理解是,编译器实际上并不向前看,而是在运行过程中创建类型变量,识别每个变量的约束,然后将实际类型分配给阅读完所有内容后的变量。 因此,在第二种情况下,当它读取 add 定义时,它会为参数创建类型变量
'a
和 'b
。 然后调用 add 5.4 3.2
,将 'a
和 'b
限制为 float
。
当您单独编译
let add x y = x + y
时,没有其他任何东西可以约束类型,因此编译器会选择 int
。
由于类型约束是在阅读整个编译单元后解决的,因此人们可能会说“总是”来回答您的问题。 您质疑的行为仅出现在 F# 交互式中,其中编译单元较小。 编译是分批完成的,而不是一次性完成。