关于Scala:如何别名化协变泛型类型参数

 2021-04-09 

How can I alias a covariant generic type parameter

以下代码无法编译(在Scala 2.11中):

1
2
3
4
5
6
7
8
9
10
case class CovariantClass[+R](value: R) {
  type T = R
  def get: R = value
}

object Main {
  def main(args: Array[String]): Unit ={
    println(CovariantClass[String]("hello").get)
  }
}

错误消息是:

1
2
3
Error:(4, 8) covariant type R occurs in invariant position in type R of type T
  type T = R
       ^

为什么不能为协变类型参数起别名?如果删除行type T = R,代码将编译并打印hello,因此别名似乎是问题所在。不幸的是,这意味着我无法为更复杂的类型创建别名,例如type T = List[R]也不会编译,尽管List是协变的。


根据scala规范:

The right-hand side of a type alias is always in invariant position.

这意味着您无法创建别名T并在右侧指定变量类型RList[R]也是相同的,因为它也是协变的。

但是,您可以提供带有类型参数的类型别名:

1
2
3
4
case class CovariantClass[+R](value: R) {
  type T[+R] = List[R]
  def get: R = value
}

如果发现自己想为类型参数R加上别名,则应该首先将其命名为其他名称。


之所以禁止,是因为它会允许不正确的程序,这始终是规则。您可以这样重写它:

1
2
3
4
case class CovariantClass[+R](value: R) {
  type T <: R
  def get: R = value
}

关于如何中断的示例,请考虑以下问题:

1
2
3
4
5
6
case class CovariantClass[+R](value: R) {
  type T = Int
  def get: R = value
  def put(x: T) {}
  def put2(x: R) {}
}

由于T的定义方式,它是不变的。这意味着它可以在协变量类型不能使用的地方使用,如上所示。请注意put会编译,但put2不会编译。