【问题标题】:Why is parameter in contravariant position?为什么参数在逆变位置?
【发布时间】:2026-01-22 01:05:01
【问题描述】:

我正在尝试在特征中使用协变类型参数来构造一个案例类,如下所示:

trait MyTrait[+T] {
  private case class MyClass(c: T)
}

编译器说:

error: covariant type T occurs in contravariant position in type T of value c

然后我尝试了以下方法,但也没有用:

trait MyTrait[+T] {
  private case class MyClass[U <: T](c: U)
}

这次的错误是:

error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U

有人可以解释为什么 T 在这里处于协变位置并为这个问题提出解决方案吗? 谢谢!

【问题讨论】:

  • 你能解释一下你真正想要做什么吗?为什么要 T 协变而不是不变?

标签: scala covariance contravariance case-class


【解决方案1】:

这是面向对象编程的一个基本特征,并没有得到应有的重视。

假设您有一个集合C[+T]+T 的意思是如果U &lt;: T,那么C[U] &lt;: C[T]。很公平。但是成为子类意味着什么?这意味着每个方法都应该工作在原始类上工作。所以,假设你有一个方法m(t: T)。这表示您可以使用任何t 并对其进行处理。但是C[U]只能用U做事,这可能不是T的全部!所以你立即反驳了你的说法,即C[U]C[T] 的子类。 不是C[T] 可以做一些C[U] 不能做的事情。

现在,你如何解决这个问题?

一种选择是使类保持不变(删除+)。另一种选择是,如果您采用方法参数,也允许 任何超类m[S &gt;: T](s: S)。现在如果T 更改为U,这没什么大不了的:T 的超类也是U 的超类,并且该方法将起作用。 (然而,你必须改变你的方法才能处理这些事情。)

使用案例类,除非您使其保持不变,否则更难以正确处理。我建议这样做,并将泛型和方差推到其他地方。但我需要查看更多详细信息,以确保这适用于您的用例。

【讨论】:

  • 感谢您的回答。但是,在这种情况下,您的解决方案对我不起作用。放弃协方差并使特征不变是可行的,但这不是我想要的。允许方法(或者在我的情况下是案例类)采用超类型也不令人满意。我很好奇为什么事情更难为案例课程做好准备。请注意,没有 case 关键字的相同代码可以正常工作。
  • @lapislazuli - 因为案例类包含创建它们的伴随方法(以T 作为参数),所以您必须遵守上述方法限制。如果您不包含case,则该类不会暗示接口中采用Ts 的方法。
  • class F[+A] { def f(x: A) = ??? } 中的xcontravariant position 中的原因是什么?是因为Function[-T1, +R中的Tcontravariance吗?
【解决方案2】:

差不多了。这里:

scala> trait MyTrait[+T] {
     |   private case class MyClass[U >: T](c: U)
     | }
defined trait MyTrait

这意味着MyClass[Any] 对所有T 都有效。这就是为什么一个人不能在那个位置使用T 的根本原因,但是证明它需要比我目前心情更多的代码。 :-)

【讨论】: