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

Mil*_*oDC 4 f# closures type-inference

这里没问题:

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)
Run Code Online (Sandbox Code Playgroud)

签名Seq.randomitems:seq<'T> -> 'T.都好.

是的,我知道我可以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)
Run Code Online (Sandbox Code Playgroud)

...即我将Random对象添加为闭包.如果我将鼠标悬停在上面random,Intellisense会告诉我签名已经变成了items:seq<obj> -> obj.

有趣的是,如果我选择代码并点击[Alt]+[Enter]以在F#Interactive中执行它,则签名显示为seq<'a> -> 'a.WTH?

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

Fyo*_*kin 8

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

有时编译器可以证明不变性.这就是你的第一个例子有效的原因:当a的右边let是一个直接的lambda表达式时,编译器可以肯定地告诉它它是不可变的,所以它让它通过.

另一个例子是let x = []- 这里编译器可以看到nil列表[]是不可变的.另一方面,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]
Run Code Online (Sandbox Code Playgroud)

但是这仍然不会做你想要的,因为在幕后,这样的定义将编译成无参数的泛型方法,每次你引用这样的"值"时,该方法将被调用并返回一个新的函数 - 用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]
Run Code Online (Sandbox Code Playgroud)