2017-07-03 15 views
9

我最近遇到了来自F#编译器的一些意想不到的行为。我能找到解决方法,但原来的行为让我感到困惑,我想看看是否有人能帮我理解是什么原因造成的。为什么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 - >诠释?很明显泛化也是导致它重复返回1的错误的原因,但为什么泛化发生在第一位呢?

需要注意的是,如果我把它定义不点名的嵌套函数,它按预期工作中给予的唯一ID:

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

但更神秘的,这个定义,F#互动不能决定nextId是否一个(obj - > int)或一个('a - > int)。当我第一次把它定义我得到

VAL nextId:(OBJ - > INT)

,但如果我只是EVAL

nextId 

我得到

VAL是:(” a - > int)

这是怎么回事,为什么我的简单函数会自动地泛化美化版?

回答

8

我同意这是非常意外的行为。我认为F#执行泛化的原因是它将help(返回时)视为fun x -> help x。调用一个需要obj的函数似乎是编译器执行泛化的一种情况(因为它知道任何东西都可以是obj)。同样的推广情况,例如:

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

这里,g变得'a -> int过,就像在你的第一个版本。我不太清楚为什么编译器会这样做,但是你所看到的可以通过以下方式来解释:1)将help当作fun x -> help x,并且2)对采用obj的调用进行概括。发生的另一件事是F#如何处理通用值 - 通用值在ML语言中通常是有问题的(这就是整个“价值限制”业务的意义),但F#在一些有限的情况下允许它 - 您可以例如写:

let empty = [] 

这定义了类型为'a list的一般值。需要注意的是,这将作为每次访问empty值时调用的函数进行编译。我认为你的第一个nextId函数以相同的方式编译 - 所以每次访问它时都会评估正文。

这可能不会回答为什么但我希望它提供了一些关于如何发生的提示 - 在其他情况下,您看到的行为可能是明智的!

5

我不知道为什么编译器决定在第一种情况下进行概括,但最终nextIdobj -> int类型与'a -> int类型之间的区别是驱动这里看似怪异的行为的原因。

对于它的价值,你可以“逼”你的第一个方案中预期的行为与另一类型的注释:

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

现在,如果你把这些值模块(如在此gist),编译并检查在ILSpy大会,你会发现该代码是除了其中用于计数器裁判细胞被实例化几乎是相同的:

  • 在具体情况下,nextId是产生一个函数,它是一个属性研究所与在模块,即nextId共享相同的计数器的所有呼叫的静态初始化裁判细胞,

  • 在通用情况下antiated在一起,nextId是一个通用的函数,产生一个函数,和ref小区内实例化它的身体,即你有一个计数器,每个电话nextId

所以在一般情况下发出的代码实际上可以在F#中呈现在这个片段:

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

的底线是,它将使感觉,当你有一个发出编译器警告这样的通用价值。一旦你知道它存在,很容易避免这个问题,但这是你不会看到的其中一件事。

相关问题