F# 编译器如何/何时预读类型推断?

问题描述 投票:0回答:4

如果我创建下面的添加功能

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# inline type-inference
4个回答
1
投票

我认为这就是简单的看法。 在第一种情况下,编译器知道

+
(它在 F# 核心中定义),并且如果它随后看到
int
,它只是选择默认类型
float
(可能在规范中的某处定义)用法,选择不同版本的
+

另一方面,编译器没有足够的信息来选择具有

Replace
成员的类型,因此无法编译该函数


1
投票

规范在第 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>] 的使用) 
例外)。


1
投票

根据很久以前对规范的阅读,我的理解是,编译器实际上并不向前看,而是在运行过程中创建类型变量,识别每个变量的约束,然后将实际类型分配给阅读完所有内容后的变量。 因此,在第二种情况下,当它读取 add 定义时,它会为参数创建类型变量

'a
'b
。 然后调用
add 5.4 3.2
,将
'a
'b
限制为
float

当您单独编译

let add x y = x + y
时,没有其他任何东西可以约束类型,因此编译器会选择
int

由于类型约束是在阅读整个编译单元后解决的,因此人们可能会说“总是”来回答您的问题。 您质疑的行为仅出现在 F# 交互式中,其中编译单元较小。 编译是分批完成的,而不是一次性完成。


0
投票

add
的情况下,编译器会将参数类型默认为
int
。如果您改为编写
let inline add x y = x + y
,那么编译器将自动将
x
y
的类型概括为实现
+
运算符的类型。这是静态解析类型参数的示例。将
add
的参数类型默认为
int
时,F# 将在当前范围内查找。

str.Replace
的情况下,方法
Replace
是字符串上的实例方法。因此,编译器必须知道
str
的类型才能对其实例方法做出任何确定。或者,在上面的示例中,
+
是一个静态函数 - 编译器预先知道涉及的类型和约束。有关 F# 类型推断的更多信息,请查看此处

© www.soinside.com 2019 - 2024. All rights reserved.