即使灵活类型用于接口约束,类型推断有时也需要显式向上转换?

时间:2014-10-10 17:36:44

标签: f#

我曾预料到灵活类型可以最大限度地减少显式向上播放的需要,但

type IB =
    abstract B : int

type A() =
    interface IB with
        member this.B = 1

let a = A()

let test (x) = x

let aa = [|a; a|]
let case1 = aa |> Array.map (fun (x: #IB) -> test (x.B))
let case2 = Array.map (fun (x: #IB) -> test (x.B)) aa

这里,最后两行有警告(比类型注释更通用)。编译器能够编译case2,但在case1失败,为什么会这样?

编译器可以推断的越详细,我需要编写的代码就越多,这感觉很奇怪。

@kvb指出有一个简单的修复,只是将lambda函数重构为非内联版本。

let fix (x: #IB) =
    test x.B

let case1 = aa |> Array.map fix
let case2 = Array.map fix

这很有效。

2 个答案:

答案 0 :(得分:3)

F#中的类型推断从左到右流动,因此管道可以破坏或修复东西并不太令人惊讶。然而,几乎总是这样的情况,即更早地获得更多类型信息是有帮助的,因此这个结果有点令人惊讶。警告是一个微妙的指示,表明你做错了什么。灵活类型似乎就像它们应该适用于许多棘手的情况,但实际上只有少数地方可以提供帮助。 Array.map针对某些特定'a -> 'b'a采用某种类型'b的函数(虽然这里有一点点细微差别,因为"特别& #34;类型可以是类型变量),所以有一个"更通用的"像#IB -> int这样的论点并不是特别有用;编译器会在编译期间选择一些特定的IB子类型 - 这就是警告尝试通信的内容。

正如我所说,你在不同的行上看到的不同结果是由于F#中的类型推断从左到右工作。在第一种情况下,有关aa类型的信息流入表达式其余部分的类型检查中,因此在编译lambda时,编译器知道唯一可能的IB子类型将起作用是A,但由于接口实现始终是隐式的,因此导致编译时错误,因为A没有公开可用的成员B。另一方面,在第二种情况下,当编译器尝试检查Array.map时,它还不知道它将应用于的数组的类型,但可以检查调用任何情况下,因为无论子类型如何,它都将支持接口方法IB.B(隐式地从任何IB子类型向{$ 1}}转换参数。然后,当这个应用于IB时,编译器将实现专门化为aa,但由于隐式upcast,这仍然可以正常工作。直观地说,似乎编译器应该能够在前一种情况下插入相同的隐式upcast,但我认为这可能只是推理算法的一个令人惊讶的结果而不是实际的错误。

也许更令人惊讶的是,一种可能的解决方法是仅使用let-bound定义(对于lambda或甚至作为A的类型限制版本)并跳过灵活类型:

Array.map

那么这里发生了什么?事实证明,编译器执行了一些名为"对函数和成员的使用的隐式插入灵活性" (F#3.1规范的第14.4.3节)完全符合你的要求。

答案 1 :(得分:1)

type IB =
    abstract B : int

type A() =
    interface IB with
        member this.B = 1

let a = A()

let test (x) = x

let aa = [|a; a|]

这里编译器知道aa是A的数组,因此不需要lambda中的类型注释,但是你必须将x转换为接口类型,因为在F#接口中明确地实现了http://msdn.microsoft.com/en-us/library/ms173157.aspx):

let d = aa |> Array.map (fun x -> test ((x:>IB).B))

这里编译lambda时的编译器不知道x的类型是什么,所以你需要一个类型注释来告诉x是IB接口的实现,所以你可以直接引用x.B属性:

let c = Array.map (fun (x: #IB) -> test (x.B)) aa