F#如何推断其他模块的类型和标签?

时间:2016-06-22 12:52:50

标签: types f# type-inference f#-4.0

这是我用来解释我的问题的最小代码示例。以下代码组织在两个文件中并编译好:

DataStruct.fs

module MyMod 
type XXX = {
    a: int
}
with
    static member GetNew =
        {
            a = -1
        }

type YYY = {
    a: float
}
with
    static member GetNew =
        {
            a = -1.0
        }


type Choice =
    | XXX of XXX
    | YYY of YYY

Program.fs

open MyMod

let generator = 
    let res = XXX.GetNew
    Choice.XXX res

let myVal : XXX = 
    match generator with
    | XXX x -> x
    | _ -> printfn "expected XXX, got sth else!"; XXX.GetNew

有趣的是,我有一个Choice类型,它有两个标签的名称与它们标记的类型相同。据我所知,这是F#中的一个常见惯例。

现在我更改DataStruct,以便将其放在命名空间中,并使MyMod成为该命名空间中的一个模块。因此,在Program.fs中,我打开命名空间并使用前缀为模块名称的所有内容:

DataStruct.fs

namespace DataStruct

module MyMod =
    type XXX = {
        a: int
    }
    with
        static member GetNew =
            {
                a = -1
            }

    type YYY = {
        a: float
    }
    with
        static member GetNew =
            {
                a = -1.0
            }


    type Choice =
        | XXX of XXX
        | YYY of YYY

Program.fs

open DataStruct

let generator = 
    let res = MyMod.XXX.GetNew
    MyMod.Choice.XXX res

let myVal : MyMod.XXX = 
    match generator with
    | MyMod.XXX x -> x
    | _ -> printfn "expected XXX, got sth else!"; MyMod.XXX.GetNew

现在Program.fs包含两个错误。在我尝试调用GetNew的两行中,它说:“字段,构造函数或成员'GetNew'未定义” 这是因为MyMod.XXX被推断为MyMod.Choice类型的并集案例。

现在,在不更改我的代码的任何结构的情况下,我只是将Choice标记重命名为与它们所代表的类型不同,并且一切正常。

DataStruct.fs如上所述,但带有

type Choice =
    | TX of XXX
    | TY of YYY

Program.fs

open DataStruct

let generator = 
    let res = MyMod.XXX.GetNew
    MyMod.Choice.TX res

let myVal : MyMod.XXX = 
    match generator with
    | MyMod.TX x -> x
    | _ -> printfn "expected XXX, got sth else!"; MyMod.XXX.GetNew

现在调用GetNew是合法的,因为MyMod.XXX被正确推断为我打算使用的类型。

现在的问题是:上面描述的问题是F#的错误或特征吗? 或者换句话说,虽然建议对标签及其类型使用相同的名称,但似乎是类型推断机制的问题。那么建议是坏的还是我使用名称空间,模块,类型和标签的方式不好?

2 个答案:

答案 0 :(得分:5)

第一段和第二段代码之间的区别在于您如何在Program.fs中打开模块:

  • 在第一段代码中,通过编写open MyMod,打开模块

  • 在您的第二个版本中,通过编写open DataStruct,您只需打开命名空间,但尚未该模块。如果将其更改为open DataStruct.MyMod,您将获得与第一个版本完全相同的行为。

我对正在发生的事情的粗略解释:

  • 打开模块后,F#会看到两个XXX浮动,并且能够根据用途消除歧义。
  • 当您使用模块名称获得资格时,您将XXX限制为XXX中定义的最新类型MyMod。第一个XXX是您的记录,第二个是从Choice派生的类,也称为XXX。例如,看一下像ILSpy这样的装配体。

更新:第二段不正确。使用模块名称限定时,F#编译器会错误地将XXX限制为DU类型,从而遮蔽记录类型。有关详细信息,请参阅我的第二个答案。

答案 1 :(得分:1)

此行为是编译器中的错误。该问题与编译器中的另一个错误有关,其中有区别的联合类型正在影响同一模块中的其他类型定义see this bug report。在您发布的代码中:错误的根本原因是名称解析中的hereMyMod.XXX被标识为引用DU类型的有效表达式。这次搜索是贪婪地完成的,codepath that searches for alternative resolutions没有被执行。

我已提交错误报告in visualfsharpfsharp