为什么这会混淆F#编译器的类型推断?

时间:2017-07-09 03:13:38

标签: f# closures type-inference

这里没问题:

str.strip

module Seq = let private rnd = Random Environment.TickCount let random = fun (items : 'T seq) -> let count = Seq.length items items |> Seq.nth (rnd.Next count) 的签名是Seq.random。一切都好。

是的,我知道我可以items:seq<'T> -> 'T,这不是重点。

重点是{I}突然被限制为let random items = [...]类型,当我这样做时:

items

...即我将seq<obj>对象添加为闭包。如果我将鼠标悬停在module Seq = let random = let rnd = Random Environment.TickCount fun (items : 'T seq) -> let count = Seq.length items items |> Seq.nth (rnd.Next count) 上,则Intellisense会向我显示签名已成为Random

有趣的是,如果我选择代码并点击random在F#Interactive中执行它,则签名显示为items:seq<obj> -> obj。 WTH ??

那么,这是怎么回事?为什么类型推断中的混淆和不一致?

1 个答案:

答案 0 :(得分:8)

这是由于所谓的Value Restriction。简而言之,语法值不能是通用的,因为它可能会在发生突变时破坏事物,并且编译器不能始终可靠地证明不变性。 (请注意,即使random在语义上是一个函数,它仍然是一个值语法,这才是最重要的)

有时编译器可以证明不变性。这就是你的第一个例子有效的原因:当let的右侧是一个直接的lambda表达式时,编译器可以肯定地告诉它它是不可变的,所以它允许这个传递。

另一个例子是let x = [] - 这里编译器可以看到nil list []是不可变的。另一方面,let x = List.append [] []将不起作用,因为在这种情况下编译器无法证明不可变性。

价值限制的这种“放松”是在F#中根据具体情况进行的。 F#编译器只能处理一些特殊情况:文字,lambda表达式等,但它没有一个完整的机制来证明一般的不变性。这就是为什么一旦你走出这些特殊情况,就不允许你拥有通用值。

您可以通过添加显式类型参数在技术上打败它。从逻辑上讲,这告诉编译器“是的,我知道它是一个通用值,这就是我的意思”。

let random<'t> : seq<'t> -> 't =
    let rnd = Random Environment.TickCount
    fun items ->
        let count = Seq.length items
        items |> Seq.nth (rnd.Next count)

let x = random [1;2;3]

但是这仍然不会做你想要的,因为在幕后,这样的定义将被编译成无参数的泛型方法,并且每次引用这样的“值”时,该方法都会被调用并返回一个新的函数 - 为每次通话都添加了全新的rnd。换句话说,上面的代码将等同于:

let random() =
    let rnd = Random Environment.TickCount
    fun items ->
        let count = Seq.length items
        items |> Seq.nth (rnd.Next count)

let x = random() [1;2;3]