记录类型推断

时间:2013-04-04 13:25:50

标签: f# type-inference

在以下代码段中

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#类型推断的限制?

4 个答案:

答案 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#类型推断的限制,还是一般类型推理算法的限制;如果你考虑一下,有两个选择:

  1. 给定记录表达式{x = 1; y = 1},将“x”和“y”字段与最后一个类型相匹配,以声明其中任何一个。这是最容易理解的,它是F#编译器如何实现记录类型推断。

  2. 尝试根据当前范围中的类型字段确定“最适合”。 (我想这就是你要问的问题。)

    然而,这种算法可能导致其他问题;特别是,对于记录表达式{x = 1; y = 1},编译器无法判断您是否要创建类型为myrec1的表达式,或者如果您打算创建myrec2的表达式而忘记了为z字段分配值。

    此外,如果您使用完全相同的字段声明两个类型,编译器应该怎么做?例如,如果你添加:

    type myrec3 = {x: int; y: int}
    
  3. 换句话说,没有免费午餐这样的东西 - 你可以增加类型推理的“力量”,但是你会在错误诊断中花费一些精确度从编译器中获取。

答案 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}