涉及Self Type时Type Member边界失效的原因及相关问题咨询
哈哈,这个问题戳中了Scala里Self Type和抽象类型成员交互的一个容易踩坑的点,我来给你掰扯得明明白白~
核心原因是:Self Type约束的是Bar实例本身的类型,而抽象类型成员My是Bar这个Trait的独立成员,两者默认没有任何关联。
先还原你大概率写的代码场景:
trait Component { type My // 无约束的抽象类型成员 } trait StuffDoer { type My // StuffDoer自己的抽象类型 def doStuff(): Unit // 标记该类型的能力 } trait Bar extends Component { self: StuffDoer => // 要求Bar的实例必须同时是StuffDoer的实现类 // 你以为这里的My会自动复用StuffDoer的My?错了! def useMy(m: My): Unit = m.doStuff() // 编译器报错:My没有doStuff方法 }
你可能误以为self: StuffDoer =>这个约束会让StuffDoer的成员(包括它的My)自动成为Bar的成员,但实际上Self Type是依赖注入式的约束,不是继承关系。Bar本身并没有继承StuffDoer的成员,它的My仍然来自父Trait Component的无约束抽象类型——编译器当然不知道这个My有doStuff方法,所以会抛出你看到的错误。
要解决这个问题,你需要显式把Bar的My和Self Type的My绑定起来:
trait Bar extends Component { self: StuffDoer => override type My = self.My // 明确关联到实例的StuffDoer类型的My def useMy(m: My): Unit = m.doStuff() // 现在编译器就知道My是StuffDoer的My,有doStuff方法了 }
这完全取决于你的代码写法:
- 如果Bar只是
extends Component,没有重写My,那My的边界和Component完全一致(如果Component里的My无约束,那它可以是任意类型)。 - 如果Bar写了
override type My <: StuffDoer,那My的上界是StuffDoer,下界是Nothing——也就是说My可以是StuffDoer的任何子类(包括StuffDoer本身),但和Self Type的StuffDoer没有自动关联。 - 如果你显式绑定
override type My = self.My,那My的边界就和StuffDoer中定义的My的边界完全一致。
恰恰相反:是Bar的重写类型成员必须遵守Component的类型约束,而非Component覆盖Bar的。
举个例子,如果Component里定义了:
trait Component { type My <: AnyRef // 给My加上界AnyRef }
那Bar里的重写必须满足My <: AnyRef,你不能写override type My <: Int(因为Int不是AnyRef的子类)。但如果Component里的My是无约束的,Bar可以自由给它加上界、下界,或者指定具体类型——只要符合Scala抽象类型的重写规则。
虽然你没写完,但我猜你可能改成了override type My = self.My或者type My = StuffDoer#My?
- 如果改成
override type My = self.My,就是把Bar的My直接绑定到实例的StuffDoer类型的My,两者完全同步,编译器自然不会再报错。 - 如果改成
type My = StuffDoer#My,这是把My绑定到StuffDoer这个Trait的类型投影,适用于你不需要和实例的具体My关联,只是想复用StuffDoer定义的My类型的场景。
内容的提问来源于stack exchange,提问作者Choppy The Lumberjack

