考虑这个带有WeakTypeTag
的人为示例:
import reflect.runtime.universe.WeakTypeTag
import reflect.runtime.universe.weakTypeOf
package object stackoverflow1 {
sealed trait Status
trait Open extends Status
trait Closed extends Status
case class Buffer[S <: Status](available: Int)
object Buffer {
def create(available: Int): Buffer[Open] = new Buffer[Open](available)
def from[S <: Status: WeakTypeTag](b: Buffer[S]): Buffer[Open] = {
weakTypeOf[S] match {
case t if t <:< weakTypeOf[Open] => b.asInstanceOf[Buffer[Open]]
case t if t <:< weakTypeOf[Closed] => new Buffer[Open](b.available)
}
}
}
}
package stackoverflow1 {
object Main extends App {
val b1 = Buffer.create(1024)
val b2 = Buffer.from(b1)
}
}
这很好,但当我将包对象的代码移动到一个特性中并将该特性混合到包对象中时,它就不再起作用了:
import reflect.runtime.universe.WeakTypeTag
import reflect.runtime.universe.weakTypeOf
package stackoverflow2 {
trait Mixin {
sealed trait Status
trait Open extends Status
trait Closed extends Status
case class Buffer[S <: Status](available: Int)
object Buffer {
def create(available: Int): Buffer[Open] = new Buffer[Open](available)
def from[S <: Status: WeakTypeTag](b: Buffer[S]): Buffer[Open] = {
weakTypeOf[S] match {
case t if t <:< weakTypeOf[Open] => b.asInstanceOf[Buffer[Open]]
case t if t <:< weakTypeOf[Closed] => new Buffer[Open](b.available)
}
}
}
}
}
package object stackoverflow2 extends Mixin
package stackoverflow2 {
object Main extends App {
val b1 = Buffer.create(1024)
val b2 = Buffer.from(b1)
}
}
错误消息:Exception in thread "main" scala.MatchError: stackoverflow2.Open (of class scala.reflect.internal.Types$ClassNoArgsTypeRef)?
为什么有一些想法?
编辑
使用抽象类型成员而不是类型参数:
import reflect.runtime.universe._
package stackoverflow3 {
trait Mixin {
sealed trait Status
trait Open extends Status
trait Closed extends Status
case class Buffer(available: Int) {
type S <: Status
def withStatus[S <: Status] = asInstanceOf[BufferAux[S]]
}
type BufferAux[S0 <: Status] = Buffer { type S = S0 }
object Buffer {
def create(available: Int): BufferAux[Open] = Buffer(available).withStatus[Open]
def from[S <: Status: WeakTypeTag](b: BufferAux[S]): BufferAux[Open] = {
weakTypeOf[S] match {
case t if t <:< weakTypeOf[Open] => b.asInstanceOf[BufferAux[Open]]
case t if t <:< weakTypeOf[Closed] => Buffer(b.available).withStatus[Open] // no need for creating new Buffer, only shows intend
}
}
}
}
}
package object stackoverflow3 extends Mixin
package stackoverflow3 {
object Main extends App {
val b1 = Buffer.create(1024)
val b2 = Buffer.from(b1)
}
}
让我们为Buffer.from
方法添加一些调试打印:
import scala.reflect.runtime.universe.showRaw
println(s"scrut = ${weakTypeOf[S]}, ${showRaw(weakTypeOf[S])}")
println(s"Open = ${weakTypeOf[Open]}, ${showRaw(weakTypeOf[Open])}")
println(s"Closed = ${weakTypeOf[Closed]}, ${showRaw(weakTypeOf[Closed])}")
这将打印出有问题的类型及其结构。也许这可以解释发生了什么:
scrut = stackoverflow2.Open, TypeRef(SingleType(ThisType(stackoverflow2), stackoverflow2.package), TypeName("Open"), List())
Open = Mixin.this.Open, TypeRef(ThisType(stackoverflow2.Mixin), TypeName("Open"), List())
Closed = Mixin.this.Closed, TypeRef(ThisType(stackoverflow2.Mixin), TypeName("Closed"), List())
好吧,这显示了所讨论类型的完全限定版本,这揭示了可能存在问题的差异。所以,反思是说stackoverflow2.Open
不是Mixin.this.Open
的亚型。嗯,但是包对象stackoverflow2
扩展了Mixin
。那一定是反映中的错误,对吧?
让我们问第三方,看看scalac对此有什么看法。我们在Buffer.from
中写一些类似val test: Mixin.this.Open = (??? : stackoverflow2.Open)
的东西并尝试编译怎么样?
Test.scala:17: error: type mismatch;
found : stackoverflow2.Open
required: Mixin.this.Open
val test: Open = (??? : stackoverflow2.Open)
^
one error found
好吧,scalac实际上同意反射,所以也许它不是一个bug。事实上,这并不是一个bug,因为如果我们创建Mixin
的另一个子类,那么该子类将拥有自己的Mixin.this.Open
副本,这将与stackoverflow2
的Mixin.this.Open
副本不兼容。这意味着stackoverflow2.Open
理所当然地不是Mixin.this.Open
的亚型。
我们有几种选择。
首先,我们可以只写t <:< weakTypeOf[stackoverflow2.Open]
,而不是t <:< weakTypeOf[Open]
。不漂亮,因为这把关于子类的知识放到了一个超类中。
其次,我们可以在weakTypeOf[Open]
上使用asSeenFrom
来将Mixin.this.Open
调整为传递到from
中的类型的容器。这将安抚子类型检查器,因为这样它将比较一棵树上的苹果和同一棵树的苹果,可以说:
type InternalType = scala.reflect.internal.SymbolTable#Type
type ApiType = Type
val pre = weakTypeOf[S].asInstanceOf[InternalType].prefix.asInstanceOf[ApiType]
val topen = weakTypeOf[Open].asSeenFrom(pre, symbolOf[Mixin])
...
case t if t <:< topen => b.asInstanceOf[Buffer[Open]]
...
第三,我们可以进行手动检查,以验证:a)我们传递的类型是在Mixin
的子类型中声明的,b)底层符号的名称是Open
或Closed
。这似乎比第二种选择更容易,但实际上我不建议这样做,因为当你自己重新实现这样的东西时,很容易忽略一些角落的情况。例如,上面提供的配方实际上是错误的,因为除了a)和b)之外,我们还需要检查c)底层符号是从Mixin
继承的,而不是重新定义的(即阴影)。我很可能忽略了其他事情,所以第三种选择绝对是风险最大的一种。