在以下代码段中
type myrec1 = {x: int; y: int}
type myrec2 = {x: int; y: int; z: int}
let p1 = {x = 1; y = 1} // Error. p1 compiler assumes p1 has the type myrec2
// It works with additional type specification
let p1: myrec1 = {x = 1; y = 1}
let p2: myrec2 = {x = 1; y = 1; z = 1}
注释的行不会编译。由于某种原因,类型检查器无法确定p1的类型应该是myrec1。是因为这种类型推断的情况根本无法解决,还是只是F#类型推断的限制?
答案 0 :(得分:9)
来自here:
最近声明的类型的标签优先于 先前声明的类型
所以,如果你这样做:
type myrec2 = {x: int; y: int; z: int}
type myrec1 = {x: int; y: int}
let p1 = {x = 1; y = 1}
然后它会起作用。
来自here(F#3.0 Spec)的阅读乐趣:
field-initializer : long-ident = expr
6.3.5记录表达式
在这种情况下,我们的field-initializer不是单个标识符,因此它使用14.1.9中的“Field Label Resolution”。
每个field-initializeri的格式为field-labeli = expri。每个field-labeli都是long-ident,必须是 解析为唯一记录类型R中的字段Fi,如下所示:
·如果field-labeli是单个标识符fld和初始值 已知类型是记录类型R< ,...,>有田野Fi的 名称fld,然后字段标签解析为Fi。
·如果field-labeli不是单个标识符或初始标识符 type是一个变量类型,然后通过解析字段标签 在field-labeli上执行字段标签解析(参见§14.1)。这个 程序产生一组字段FSeti。这一组的每个元素 具有相应的记录类型,从而产生一组记录 类型RSeti。所有RSeti的交集必须产生单个记录 输入R,然后每个字段解析为R中的相应字段。
14.1.9字段标签解析
我们的long-ident是一个FieldLabel,因此使用8.4.2中描述的FieldLabels表进行查找。
字段标签解析指定如何在{field1 = expr;中解析诸如field1之类的标识符; ... fieldN = expr}。字段标签解析通过以下步骤进行:
1。在Types表和FieldLabels表(第8.4.2节)中查找所有可用类型的所有字段。
2。返回字段声明集。
8.4.2名称解析和记录字段标签
如此处所述,FieldLabels表用于成员的名称解析(14.1)。
对于记录类型,添加记录字段标签field1 ... fieldN 到当前名称解析环境的FieldLabels表 除非记录类型具有RequireQualifiedAccess属性。 FieldLabels表中的记录字段标签在其中起特殊作用 成员的名称解析(第14.1节):表达式的类型可以是 从唱片公司推断。例如:type R = {dx:int; DY: int} let f x = x.dx // x被推断为具有类型R在此示例中, 查找.dx被解析为字段查找。
14.1.4表达式中的名称解析 这部分似乎有点模糊,但我认为此时它使用名称解析。如最后所述,如果有多个项目,则返回第一个项目。
给定输入long-ident,环境env和可选的n个计数 后续类型参数的数量< ,...,>,名称解析 表达式计算包含对的解释的结果 长IDENT< ,...,>前缀作为值或其他表达式项,以及 残留路径休息。表达式中的名称解析如何进行取决于 关于long-ident是单个标识符还是由更多标识符组成 比一个标识符。如果long-ident是单个标识符ident:
1。在ExprItems表中查找ident。返回结果并清空。
2。如果ident没有出现在ExprItems表中,请在Types表中查找,其中通用arity与n匹配(如果可用)。 返回此类型并清空。
3。如果ident没有出现在ExprItems表或Types表中,则失败。
...
如果表达式包含歧义,则表达式中的名称解析 返回进程生成的第一个结果。
您感兴趣的部分是上面的最后一行:“返回流程生成的第一个结果”。
答案 1 :(得分:4)
此行为是设计使然。我不能说它是F#类型推断的限制,还是一般类型推理算法的限制;如果你考虑一下,有两个选择:
给定记录表达式{x = 1; y = 1}
,将“x”和“y”字段与最后一个类型相匹配,以声明其中任何一个。这是最容易理解的,它是F#编译器如何实现记录类型推断。
尝试根据当前范围中的类型字段确定“最适合”。 (我想这就是你要问的问题。)
然而,这种算法可能导致其他问题;特别是,对于记录表达式{x = 1; y = 1}
,编译器无法判断您是否要创建类型为myrec1
的表达式,或者如果您打算创建myrec2
的表达式而忘记了为z
字段分配值。
此外,如果您使用完全相同的字段声明两个类型,编译器应该怎么做?例如,如果你添加:
type myrec3 = {x: int; y: int}
换句话说,没有免费午餐这样的东西 - 你可以增加类型推理的“力量”,但是你会在错误诊断中花费一些精确度从编译器中获取。
答案 2 :(得分:4)
如果您想要具有类似命名字段的记录类型,区分它们的方法是:
let p1 = {myrec1.x = 1; y = 1}
答案 3 :(得分:1)
由于没有类型注释,编译器会从标签中推断出记录类型,但第一种类型的记录类型与第二种类型的标签不完全不同,因此后者优先,p1推断为myrec2类型。使用不同的标签以避免类型注释并获得预期的类型推断行为:
type myrec1 = {x1: int; y: int}
type myrec2 = {x2: int; y: int; z: int}
let p1 = {x1 = 1; y = 1}