10

有人可以向我解释为什么示例 1 可以编译但示例 2 没有吗?

示例 1:

trait Foo[+A]
trait Bar[A] extends Foo[A]

示例 2:

trait Foo[A[+_]]
trait Bar[A[_]] extends Foo[A]

示例 2 无法编译并出现以下错误消息:“类型参数 (A) 的种类不符合 trait Foo 中类型参数 (类型 A) 的预期类型。A 的类型参数与类型 A 的预期参数不匹配: type _(在 trait Bar 中)是不变的,但 type _(在 trait Foo 中)被声明为协变的”

4

2 回答 2

7

在示例 1 中,+Ais 不是对 A 的约束。任何类型都可以是 Foo 的参数。它是对 的约束Foo。表示在 的接口中FooA只能出现在协变的位置(简而言之,可能是方法结果,而不是方法参数)。

Bar没有协变意味着 Bar 的接口不满足相同的约束(或者至少不宣传它),所以可能在 中,添加了一个带参数Bar的方法。A这很常见。例如,有collection.Seq[+A],它由 扩展collection.mutable.Seq[A]。这不会造成不健全的问题。如果Y <: X, aBar[Y]不是 a Bar[X],但它仍然是 a Foo[X]

另一方面,在示例 2 中,A[+_]是对 的约束A。只有具有协变类型参数的类型才可以是 Foo 的参数。的代码Foo可能会利用该约束,例如将 分配A[String]A[Any]的代码中的某处Foo

然后允许Bar用非协变类型实例化是不合理的。继承自的代码Foo仍然可以被调用并将 分配A[String]A[Any], whenA不再是协变的。

只要您允许解除对通用参数的约束,就会发生非常相似的可靠性问题。假设你有Foo[X <: Ordered[X]](或Foo[X : ordering])并且你有一个方法sortFoo. 假设那Bar[X] extends Foo是允许的。Bar可能需要订购的代码中没有任何内容X,但仍然sort可以调用,并且肯定会与无法订购的项目行为不端。


关于您在 Traversable[+Ele​​m, +Col[+_]] 的评论中的问题,并将其扩展为创建一个可变类:

从技术上讲,是的,你可以扩展它并var youCanMutateThat : Elem = _在里面放一些。我想这不是你要找的。但我想你的计划是允许使用带有可变 Col 的扩展,我认为你不能这样做,原因如上所述。但是,为什么你首先有 Col[+_] 约束?

于 2014-03-20T13:37:08.643 回答
2

在示例 2 中需要为 A[_] 添加协方差

trait Bar[A[+_]] extends Foo[A]

因为Foo期望协变类型作为参数(+_ 是替换类型约束的一部分)并且继承类型需要保证参数是协变的(替换类型的限制)。

在示例 1 中,您定义Foo(如容器)通过参数是协变的,并且继承的容器可以是不变的(对替换类型没有限制)

Martin-Löf 类型理论中的更多细节(谓词参数多态性

于 2014-03-20T13:08:46.313 回答