为什么类型字段的上限在强制转换后丢失(参见说明中的示例)?

时间:2012-07-01 23:01:53

标签: scala types

定义以下内容后:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
}

正如所料,我们无法使用T = Int创建对象:

scala> val a = new A with B {type T = Int}
<console>:9: error: overriding type T in trait B with bounds >: Nothing <: String;
 type T has incompatible type
       val a = new A with B {type T = Int}
                                  ^

正如预期的那样,我们可以创建一个T = String的对象:

scala> val a = new A with B {type T = String}
a: A with B{type T = String} = $anon$1@692dec

scala> a.print("test")
test

将值a转换为类型A with B后,调用print方法时出错。类型字段T似乎丢失了有关类型(?)的信息。

scala> val b = a.asInstanceOf[A with B]
b: A with B = $anon$1@1927275

scala> b.print("test")
<console>:15: error: type mismatch;
 found   : java.lang.String("test")
 required: b.T
              b.print("test")
                      ^

问题1:为什么有关类型字段T的信息在演员投放后丢失了?

好的,我们再次尝试使用强制转换,明确将类型字段T设置为String类型:

scala> val c = a.asInstanceOf[A with B {type T = String}]
c: A with B{type T = String} = $anon$1@1927275

scala> c.print("test")
test

好的,这很有效。

现在让我们尝试疯狂的事情:

scala> val d = a.asInstanceOf[A with B {type T = Int}]
d: A with T{type T = Int} = $anon$1@1927275

scala> d.print(3)
3

问题2:嗯?特性B限制类型T是String的子类型,但现在print方法适用于整数。为什么这有效?

2 个答案:

答案 0 :(得分:5)

问题1 - “将我们的值a转换为带有B的类型A后,我们在调用print方法时出错。”演员之后有什么关于T的信息?这恰恰是B

中的内容

type T <: String

因此,类型不知道,只是它的上限。以下说明禁止printA with B的调用的原因:

trait X
trait Y extends X { def hallo() = () }

trait A {
  type T
  def test(t: T) = ()
}

trait B extends A {
  type T <: X
}

val y = new A with B { type T = Y; override def test(t: T): Unit = t.hallo() }
y.test(new X {})     // refused -- rightfully
y.test(new Y {})     // ok

val yc = y: A with B // note how we can cast type safe this way!
yc.test(new X {})    // refused -- rightfully (would try to call inexistent 'hallo')

所以这是在逆变(方法论证)位置发生的类型会发生什么问题。如果您通过缩小下限(即B)来定义type T >: X,即使test未修复,也可以调用T


问题2 - 当然有效。您可以使编译器允许使用类型转换进行任何调用。转换为A with B {type T = Int}后,强制编译器接受T = Int。现在,您调用的toString方法是为java.lang.Object定义的,由于A的通用结构,您的Int被列入java.lang.Integer,因此调用toString时,您没有看到任何运行时问题。

但是认为你在这里做正确的事情是错误的。例如:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
  override def print(p: T) = println(p.toUpperCase) // !
}

val a = new A with B { type T = String }
val b = a.asInstanceOf[A with B { type T = Int }]
b.print(33)  // bang -- runtime class cast exception

答案 1 :(得分:2)

  

问题1:为什么在演员表之后关于类型字段T的信息会丢失?

因为你为什么要演员?演员意味着:嘿编译器,它是我无所不能的程序员。我想做一些你不理解的事情。这样做 - 没有矛盾!

这就是编译器所做的 - 它会删除他所有的知识,因为这就是你告诉他的。

类型成员是一些编译时信息,如果你用你的强制转换删除它,你也删除了编译器的知识。

  

问题2:[...]为什么这有效?

因为你是无所不能的程序员而且编译器服从。它没有机会证明你告诉他的是否正确。

好吧,最后一句话并不完全正确,因为我们的智能编译器知道程序员永远不会无所不能 - 即使有人相信这一点。因此,它并不总是信任程序员。它会玩自己的游戏并遵循自己的规则来保护程序员。但即使我们的编译器也不是无所不能的。有时它必须信任程序员的指令 - 就像大多数演员一样。