17

F# 具有称为“类型扩展”的功能,使开发人员能够扩展现有类型。有两种类型的扩展:固有扩展可选扩展。第一个类似于 C# 中的部分类型,第二个类似于方法扩展(但更强大)。

要使用内部扩展,我们应该将两个声明放入同一个文件中。在这种情况下,编译器会将两个定义合并为一种最终类型(即,这是一种类型的两个“部分”)。

问题是这两种类型对不同的成员和值有不同的访问规则:

// SampleType.fs
// "Main" declaration
type SampleType(a: int) =
    let f1 = 42
    let func() = 42

    [<DefaultValue>]
    val mutable f2: int
    member private x.f3 = 42
    static member private f4 = 42

    member private this.someMethod() =
        // "Main" declaration has access to all values (a, f1 and func())
        // as well as to all members (f2, f3, f4)
        printf "a: %d, f1: %d, f2: %d, f3: %d, f4: %d, func(): %d"
            a f1 this.f2 this.f3 SampleType.f4 (func())

// "Partial" declaration
type SampleType with

    member private this.anotherMethod() =
        // But "partial" declaration has no access to values (a, f1 and func())
        // and following two lines won't compile
        //printf "a: %d" a
        //printf "f1: %d" f1
        //printf "func(): %d" (func())

        // But has access to private members (f2, f3 and f4)
        printf "f2: %d, f3: %d, f4: %d"
            this.f2 this.f3 SampleType.f4

我阅读了 F# 规范,但没有找到任何想法为什么 F# 编译器区分值和成员声明。

在 F# 规范的 8.6.1.3部分中说“实例定义定义的函数和值在词法范围内(因此隐式私有)到正在定义的对象。”。部分声明可以访问所有私有成员(静态和实例)。我的猜测是,“词法范围”规范作者专门指的是“主要”声明,但这种行为对我来说似乎很奇怪。

问题是:这种行为是故意的,其背后的理由是什么?

4

2 回答 2

10

这是一个很好的问题!正如您所指出的,规范说“本地值在词法范围内限定为正在定义的对象”,但查看 F# 规范,它实际上并没有定义在这种情况下词法范围的含义。

如您的示例所示,当前行为是对象定义的词法范围只是主要类型定义(不包括内在扩展)。我对此并不感到惊讶,但我看到其他解释也很有意义......

我认为这样做的一个很好的理由是这两种扩展应该表现相同(尽可能),并且您应该能够根据需要将代码从使用一种重构为使用另一种。这两种类型的区别仅在于它们在封面下的编译方式。如果一种允许访问词法范围而另一种不允许访问,则此属性将被破坏(因为,扩展成员在技术上无法做到这一点)。

也就是说,我认为这可以(至少)在规范中得到澄清。报告此问题的最佳方法是将电子邮件发送fsbugsmicrosoftdot com

于 2013-04-05T11:35:57.310 回答
3

我把这个问题发给了fsbugsat microsoftdot com,得到了 Don Syme 的以下回答:

嗨,谢尔盖,

是的,这种行为是故意的。当您在类范围中使用“let”时,标识符在类型定义上具有词法范围。该值甚至可能不会放在一个字段中——例如,如果一个值没有被任何方法捕获,那么它就成为构造函数的本地值。这种分析是在班级本地完成的。

我了解您希望该功能像 C# 中的部分类一样工作。然而,它只是不能那样工作。

我认为应该在规范中更清楚地定义术语“词法范围”,因为否则当前的行为也会令其他开发人员感到惊讶。

非常感谢唐的回应!

于 2013-04-13T08:51:37.890 回答