【问题标题】:Why does declaration order matter for generic members?为什么声明顺序对通用成员很重要?
【发布时间】:2021-02-25 06:38:17
【问题描述】:

今天我注意到以下内容无法编译:

open System

type MyType() =        
    member this.Something() =
        this.F(3)
        this.F("boo") 
        //     ^^^^^
        // This expression was expected to have type 'int' but here has type 'string'
        
    member private this.F<'T> (t:'T) =
        //                ^^
        // This type parameter has been used in a way that constrains it to always be 'int'
        // This code is less generic than required by its annotations because the explicit type variable 'T' could not be generalized. It was constrained to be 'int'.
        Console.WriteLine (t.GetType())

但是只要改变申报顺序,就没有问题了。

open System

type MyType() =        
    member private this.F<'T> (t:'T) =
        Console.WriteLine (t.GetType())
        
    member this.Something() =
        this.F(3)
        this.F("boo")

我花了很长时间才弄清楚,因为我没想到声明顺序对班级成员很重要。这是预期的行为吗?

【问题讨论】:

    标签: f#


    【解决方案1】:

    这是 F# 类型推断工作方式的一个微妙副作用。我认为没有比重新排序定义更好的解决方法了。

    为了提供更多背景信息,类的成员会自动被视为相互递归(意味着它们可以相互调用)。与模块中的多个类型或多个函数不同,您无需使用 rec 关键字显式声明。

    但是,问题不仅限于课程。您可以使用简单的函数获得完全相同的行为。最简单的例子是:

    let rec test() = 
      f 3       // Warning: causes code to be less generic
      f "boo"   // Error: string does not match int
    and f (arg:'a) = ()
    

    以相反的顺序,这很好用:

    let rec f (arg:'a) = ()
    and test() = 
      f 3
      f "boo"
    

    问题在于类型检查器从上到下分析代码。在第一种情况下,它:

    • 看到f 3 并推断fint -&gt; unit 类型
    • 看到f "boo" 并报告类型错误
    • 看到 f (arg:'a) 并意识到它过早地使用了比需要的更具体的类型(并报告了各种警告)。

    在第二种情况下,它:

    • 看到f (arg:'a) 并推断f'a -&gt; unit 类型
    • 看到 f 3f "boo" 并使用适当的类型实例化

    类型检查器不能更聪明的主要原因是类型检查器只做“泛化”(即找出一个函数是泛型的)它分析整个主体之后函数(或整个递归块)。如果它在体内遇到更具体的用途,它永远不会进入这个泛化步骤。

    【讨论】:

    • 我明白了。好吧,至少它是一致的并且不限于类。我想这不会被认为是一个错误,尽管这绝对让我感到惊讶。
    【解决方案2】:

    就像文件顺序在 F# 中很重要一样,行顺序也很重要。通常,文件后面声明的任何内容都不适用于该文件前面的表达式。 这需要一段时间来适应,但最终是防止您意外编写意大利面条代码的好方法。 有一些例外:

    但我认为没有任何机制允许稍后在文件中放置通用声明,并且可能只是您需要了解 F# 的工作原理。

    【讨论】:

    • 这对非泛型成员有效但对泛型成员造成类型约束问题似乎很奇怪。我正在为这种行为寻找合理的理由;如果找不到,我会在 Github 上打开一个问题。
    • 因为类型是根据第一次使用推断出来的,除非明确声明为泛型。
    猜你喜欢
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 2017-08-05
    • 2013-05-18
    • 1970-01-01
    • 2014-08-08
    相关资源
    最近更新 更多