Scala 类型成员差异
Scala type members variance
考虑这个简短的片段:
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]
?
此外,如果我们忽略错误,您能否提供一个用例来说明此类型定义可能存在的问题?
对于任何 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]
。但是等等,prodTable
和tupleTable
是同一个对象,所以他们的成员应该是一样的,所以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
对于 Table[Product]
是某种类型 <: Seq[Product]
这对于 Seq[(String, String)]
是正确的并且所有歧义都消失了。
这是一个反例:
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)
斯卡斯蒂 link:https://scastie.scala-lang.org/smarter/Cno5sLZyTSybDC80uDt2hQ。感谢@smarter。
考虑这个简短的片段:
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]
?
此外,如果我们忽略错误,您能否提供一个用例来说明此类型定义可能存在的问题?
对于任何 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]
。但是等等,prodTable
和tupleTable
是同一个对象,所以他们的成员应该是一样的,所以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
对于 Table[Product]
是某种类型 <: Seq[Product]
这对于 Seq[(String, String)]
是正确的并且所有歧义都消失了。
这是一个反例:
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)
斯卡斯蒂 link:https://scastie.scala-lang.org/smarter/Cno5sLZyTSybDC80uDt2hQ。感谢@smarter。