【问题标题】:Why is F# compiler sometimes incorrectly generalizing functions?为什么 F# 编译器有时会错误地泛化函数?
【发布时间】:2017-12-07 04:28:11
【问题描述】:

我最近遇到了 F# 编译器的一些意外行为。我能够找到解决方法,但最初的行为让我感到困惑,我想看看是否有人可以帮助我了解导致它的原因。

我定义为非泛型的函数正在变为泛型,这干扰了函数在多个调用之间共享状态的能力。我将我的用例简化为以下内容:

let nextId =
  let mutable i = 0
  let help (key:obj) =
    i <- i + 1
    i
  help
nextId "a" // returns 1
nextId "b" // also returns 1!!!!

为什么 nextId 的类型是 'a -> int 而不是 obj -> int?显然,泛化也是造成重复返回 1 的错误的原因,但是为什么泛化首先会发生呢?

请注意,如果我在没有命名嵌套函数的情况下定义它,它会在提供唯一 ID 时按预期工作:

let nextId =
  let mutable i = 0
  fun (key:obj) ->
    i <- i + 1
    i
nextId "a" // returns 1
nextId "b" // returns 2

但更神秘的是,有了这个定义,F# Interactive 无法确定 nextId 是 (obj -> int) 还是 ('a -> int)。当我第一次定义它时,我得到了

val nextId : (obj -> int)

但如果我只是评估

nextId

我明白了

验证它:('a -> int)

这里发生了什么,为什么我的简单函数会自动泛化?

【问题讨论】:

    标签: generics f# automatic-generalization


    【解决方案1】:

    我不知道为什么编译器决定在您的第一个场景中进行泛化,但最终 nextId 类型为 obj -&gt; int'a -&gt; int 之间的区别是导致这里看似奇怪的行为的原因。

    对于它的价值,您可以在第一个场景中使用另一个类型注释“强制”预期行为:

    let nextId : obj -> int =
        let mutable i = 0
        let help (key:obj) =
            i <- i + 1
            i
        help
    

    现在,如果您将这些值放入模块中(如在此 gist 中),在 ILSpy 中编译并检查程序集,您会发现代码几乎相同,除了计数器的 ref 单元被实例化的位置:

      1234563 p> 1234563

    因此,在泛型情况下发出的代码实际上可以使用这个 sn-p 在 F# 中呈现:

    let nextId () =
        let mutable i = 0
        fun key ->
            i <- i + 1
            i
    

    底线是,当您有这样的泛型值时,发出编译器警告是有意义的。一旦您知道问题的存在就很容易避免它,但这是您不会看到的那些事情之一。

    【讨论】:

      【解决方案2】:

      我同意这是非常出乎意料的行为。我认为 F# 执行泛化的原因是它将help(返回时)视为fun x -&gt; help x。调用采用obj 的函数似乎是编译器执行泛化的一种情况(因为它知道任何东西都可以是obj)。例如,同样的概括发生在:

      let foo (o:obj) = 1
      let g = fun z -> foo z
      

      在这里,g 也变成了'a -&gt; int,就像在您的第一个版本中一样。我不太清楚为什么编译器会这样做,但您所看到的可以通过 1) 将 help 视为 fun x -&gt; help x 和 2) 概括调用 obj 来解释。

      正在发生的另一件事是 F# 如何处理泛型值 - 泛型值在 ML 语言中通常是有问题的(这就是整个“值限制”业务的意义所在),但 F# 在某些有限的情况下允许它 - 你可以示例写:

      let empty = []
      

      这定义了'a list 类型的通用值。需要注意的是,它会被编译为每次访问 empty 值时调用的函数。我认为您的第一个 nextId 函数以相同的方式编译 - 因此每次访问它时都会评估主体。

      这可能无法回答为什么,但我希望它提供更多关于这是如何发生的提示 - 以及在其他情况下您看到的行为可能是明智的!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多