今天我在 Scala 中遇到了一些奇怪的情况,当我试图优化抽象类型成员上的类型边界时。
我有两个特征,它们定义类型成员的边界并将它们组合到一个具体的类中。这工作正常,但是当与特征组合匹配/铸造时,两个 TypeBounds 中只有一个是"活跃的",我很难理解为什么......
我试图准备一个例子:
trait L
trait R
trait Left {
type T <: L
def get: T
}
trait Right {
type T <: R
}
现在,如果我将这两个特征结合在一个具体的类中
val concrete = new Left with Right {
override type T = L with R
override def get: T = new L with R {}
}
我可以按预期通过获取访问我的会员
// works fine
val x1: L with R = concrete.get
但是如果我投射到(左与右)或模式匹配,我将无法再访问该成员。根据顺序,我从左或右获得类型边界,而不是两者的组合。
// can only access as R, L with R won't work
val x2: R = concrete.asInstanceOf[Left with Right].get
// can only access as L, L with R won' work
val x3: L = concrete.asInstanceOf[Right with Left].get
我知道左与右与右不是一回事,但在这两种情况下都包含两个类型边界,那么为什么我只能得到一个工作?
谁能阐明为什么会发生这种情况?
第二个类型成员将覆盖第一个类型成员。
trait L
trait R
trait Left {
type T <: L
def get: T
}
trait Right {
type T <: R
}
object X {
type LR = Left with Right // Right#T overrides Left#T, LR#T is now <: R
type RL = Right with Left // Left#T overrides Right#T, LR#T is now <: L
val concrete = new Left with Right {
override type T = L with R
override def get: T = new L with R {}
}
// ok
val r: R = concrete.asInstanceOf[LR].get
val l: L = concrete.asInstanceOf[RL].get
// ok
implicitly[LR#T <:< R]
implicitly[RL#T <:< L]
// doesn't compile, LR#T is a subclass of R because Right#T overrides Left#T
implicitly[LR#T <:< L]
// doesn't compile, RL#T is a subclass of L because Left#T overrides Right#T
implicitly[RL#T <:< R]
}
在"具体"中,你用 L with R
覆盖类型成员,但是当你把它转换为Left with Right
时,你会失去这种细化,T 变成 _ <:L 或 _ <:R,具体取决于特征的顺序。
由于可以覆盖类型成员,因此如果向上转换(例如,向上转换为 LR 或 RL),则会丢失在混凝土中应用的细化。你可以说具体同时是 RL 和 LR,但是当你把它转换为 LR 或 RL 时,你会丢失你在另一个中拥有的信息
除了TrustNoOne的答案之外,我可以建议遵循一些限制的解决方法。您可以设计自己的类型组合器,而不是with
来克服类型重写。
trait L
trait R
trait Base {
type T
def get: T
}
trait Nil extends Base{
type T = Any
}
trait ~+~[X[_ <: Base] <: Base, Y[_ <: Base] <: Base] extends Base {
type T = Y[X[Nil]]#T
}
trait Left[B <: Base] extends Base {
type T = B#T with L
}
trait Right[B <: Base] extends Base {
type T = B#T with R
}
val concrete = new (Left ~+~ Right) {
def get: T = new L with R {}
}
val x1: L with R = concrete.get
val x2: R = concrete.asInstanceOf[Left ~+~ Right].get
val x3: L = concrete.asInstanceOf[Right ~+~ Left].get
这段代码现在可以成功编译,但请注意,我们不能在新类型中合并组合类型的命名空间,因此所有已知方法都应该在 Base
中定义或通过类似于无形HList
的机制在某个类型类中派生