Scala类型成员差异

时间:2015-11-01 02:57:48

标签: scala variance higher-kinded-types

考虑这个简短的片段:

trait Table[+A] {
  type RowType = Seq[A]
}

Scala 2.11.7编译器出现以下错误:

covariant type A occurs in invariant position in type Seq[A] of type RowType

为什么A被认为在Seq[A]中处于不变位置而Seq本身被定义为trait Seq[+A]

另外,如果我们忽略错误,您能否提供一个用例来说明此类型定义可能存在的问题?

2 个答案:

答案 0 :(得分:8)

对于任何B <: A,您的Table[B]#RowType将比Table[A]#RowType更具体。更具体的含义并不相同,因此编译器将类型别名的参数视为不变位置。

你如何解决这个问题。

抽象成员

你可以将你的类型定义为抽象,这意味着你应该在以后定义它并且可能遇到同样的问题,但是在trait Table级别这样的定义是正确的

trait Table[+A] {
  type RowType <: Seq[A]
}

混凝土高级型

您可以定义参数化类型成员,这可能会导致您更改使用此类型的方式,但在大多数情况下应该执行此任务。

trait Table[+A] {
  type RowType[+X] = Seq[X]
}

关于类型成员差异

不是我最强的领域,但我试着描述我的想法。

假设你有

trait Table[+A] {
  type RowType = Seq[A]
}

def mkTable[A]: Table[A]  = new Table[A] {}

然后你做了以下

val tupleTable = mkTable[(String, String)]
val prodTable: Table[Product] = tupleTable 

那么prodTable.RowType会是什么?

如果您的定义应该是Seq[Product]。但等等,prodTabletupleTable是同一个对象,因此其成员应该相同,因此prodTable.RowType应为Seq[(String, String)]

但是如果你改成第一种方法,比如

trait Table[+A] {
  type RowType <: Seq[A]
}

def mkTable[A]: Table[A]  = new Table[A] {
  type RowType = Seq[A]
}

编译器会知道RowType的{​​{1}}属于某种类型Table[Product]<: Seq[Product]属实,所有歧义都消失了。

答案 1 :(得分:2)

这是一个反例:

import scala.annotation.unchecked.uncheckedVariance

class Foo[+S](x: S) {
  type Shape = S @uncheckedVariance // THIS IS BAD
}
object Test {
  def main(args: Array[String]): Unit = {
    val fs: Foo[String] = new Foo("")
    
    def cast(x: Foo[Any]): x.Shape = 1 // This typechecks since x.Shape =:= Any
    
    val s: String = cast(fs) // This typechecks because Foo[String] <: Foo[Any], but results in a ClassCastException
    
  }
}

抛出:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Test$.main(main.scala:12)

Scastie 链接:https://scastie.scala-lang.org/smarter/Cno5sLZyTSybDC80uDt2hQ。归功于@smarter。