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

How does F# infer types and tags from other modules?

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

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 的两行中,它说:"The field, constructor or member 'GetNew' is not defined" 这是因为 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#的错误还是特性? 或者换句话说,虽然建议对标签及其类型使用相同的名称,但这似乎是类型推断机制的问题。那么这个建议是错误的还是我以错误的方式使用名称空间、模块、类型和标签?

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

  • 在您的第一个代码中,通过编写 open MyMod,您可以打开模块

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

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

  • 打开模块后,F# 会看到两个 XXX 浮动,并且能够根据使用来消除歧义。
  • 当您使用模块名称进行限定时,您将 XXX 限制为 MyMod 中定义的最新类型 XXX。第一个 XXX 是您的记录,第二个是从 Choice 派生的 class,也称为 XXX。例如,在 ILSpy 中查看您的程序集。

更新:第二段不正确。当使用模块名称限定时,F# 编译器错误地将 XXX 限制为 DU 类型,从而隐藏记录类型。详情请看我的第二个回答。

此行为是编译器中的错误。该问题与编译器中的另一个错误有关,其中受歧视的联合类型正在隐藏同一模块中的其他类型定义,see this bug report. In the code you had posted: The root cause of the bug is here in the name resolution. MyMod.XXX is identified as a valid expression that refers to a DU type. This search is done greedily, the codepath that searches for alternative resolutions 未执行。

我已提交错误报告in visualfsharp and fsharp