A self-type requires any implementation using the trait B to also inherit from IA. So there is always a play() method to override when used. Without the self-type, trait B could be used with a class that doesn’t also use IA.
That said, I agree that using a selftype to override a method from that trait would be surprising and I don’t think I’d recommend using it that way.
A self-type requires any implementation using the trait B to also inherit from IA.
For example:
trait IA {
def play()
}
trait IAImpl extends IA {
override def play(): Unit = {
println("IAImpl play")
}
}
trait B {
this: IA =>
}
class MyClass extends B with IAImpl // MyClass using the trait B to also inherit from IA.
Obviously, this code can compile successfully.
I can even override play in MyClass:
class MyClass extends B with IAImpl {
override def play(): Unit = {
println("MyClass play")
}
}
But if I remove the IAImpl in the definition of MyClass as following:
class MyClass extends B { // <---- illegal inheritance;
override def play(): Unit = {
println("MyClass play")
}
}
Then compilation failed.
In other words, the method play should be from IAImpl, not B.
So IMO, the Scala compiler should not let programmers to override the play in B, because there is no public method play in B or its super class/traits.
Another surprising is that even if I override the play method in B, we still need mix the IAImpl to make compiler comfort.
See following code:
trait IA {
def play()
}
trait B {
this: IA =>
override def play(): Unit = {
println("B play")
}
}
class MyClass extends B // <--- illegal inheritance
As you see, it still failed, the overriding play in B has no effect at all.
Do you mean that there are two semantics of override in Scala ?
If the play is not in the self-type, the semantics of override is static bounded.
That means the overridden play must be in the super class (or super traits) of currently defining class or trait.
If the play is in the self-type , the semantics of override is dynamic bounded.
That means the overridden play does not have to be in the super class (or super traits) of currently defining class or trait, but it must be overridden in the eventual mixed class. So we can override it in B in advance.
No, what I mean is that it is illegal to extend B without also extending IA, because B depends on IA (that’s the semantic meaning of this: IA =>), so the definition
Unlike java traits are modules that contain working code. They are not Java interfaces. Scala allows composition. If I say class X extends IA with IB it means that class X can use the methods in the IA and IB traits.
When we use the self type in trait B we are simply saying, this module requires some implementation with those signatures. If it is already available in a trait with which we extend B, then we can instantiate that class. If not we must provide either a class or trait with the required implementation.
Traits are used for composition and not for class extension via overloading. Experiment with the code below to see this. This may be used, for example, for simple dependency injection (earlier versions of MacWire)
trait IA {
def play(): Unit
}
trait IB {
def work(): Unit = {
println("IB work")
}
}
trait B {
this: IA with IB =>
def needPlay(): Unit = {
play()
println("B play" )
}
def needWork(): Unit = {
work()
println("B work")
}
}
class MyClass() extends IA with IB {
override def play(): Unit = {
println("MyClass play")
}
override def work(): Unit = {
println("MyClass work")
}
}
trait IA1 extends IA {
def play(): Unit = {
println("IA1 play")
}
}
class HisClass() extends B with IA1 with IB {
def needPlayAndWork(): Unit = {
play()
work()
println("HisClass" )
}
}
trait IA2 extends IA {
def play(): Unit = {
println("IA2 play")
}
}
class SomeClass() extends B with IA2 with IB {
def usePlayAndWork(): Unit = {
play()
work()
println("SomeClass" )
}
}
Keep in mind, this is an unusual usage – I can’t recall seeing a usage of override on a self-type like this before.
That said, I think it’s all as-designed, and there’s nothing “dynamic” about it: it’s just a constraint that you are putting on the compiler, that this trait can only be mixed into classes that provide the specified self-type. This means that the compiler can assume that this type exists in the final class, and compile accordingly.
So in your example, you are saying that trait B can only be mixed into classes that also include trait IA, and it will override the meaning of the play() method in those classes. That’s not a common usage, but there’s nothing terribly weird or dynamic about it, and it doesn’t change the meaning of override, because the resolution of the final meaning comes from the compilation of the class, which has all the components to figure out how they fit together.
Remember, traits can’t be instantiated: they’re kind of ephemeral in that sense. What matters at runtime is classes, and you get an error if the compiler can’t prove that the class, as a whole, makes sense.
Exactly, it tells the compiler to error out if there’s not play() in IA.
That in it self isn’t very useful here, but it is good practice to use override when implementing methods defined in inherited classes/traits.
The word “dynamic” I mentioned before just means that the overridden method will be determined when the trait is mixed into a final class. It has nothing to do with real runtime dynamic.
Huh – okay, so it is. That’s an unfortunate usage, IMO, given that the word “dynamic” is much more commonly used to contrast with static types in the Scala world: the word’s being overloaded here. I don’t think I’ve ever noticed this particular usage before – I might recommend to Bill that they change it for the next edition.
Anyway: in that sense, sure – super-call resolution is kind of complex when traits are involved, which is why they are using the word “dynamic”, and now I understand your question above. I think that technically overrides are always “dynamic” in this sense if they are defined in traits – I don’t think the self-type changes that.
Keep in mind, the difference between a self-type and a plain old inherited trait is pretty subtle, and in most ways they tend to work the same – enough so that people sometimes question whether it’s worth having self-types in the language. Aside from the difference in how they are declared, and the way that the self-type puts an extra requirement on the inheriting classes (to mix this type in), they’re usually identical, and it’s usually best to think of them that way.
So from the level of trait B in your example, I think the override behaves precisely the same way as it would if B inherited directly from IA, intentionally. (The aspect I’m not quite certain about here is the linearization order of the overrides in the case of self-types. I suspect that it’s exactly the same as it would be if B inherited from IA, but I’ve never tried the experiment.)
That’s true in a sense, but kind of saying the same thing – super calls go up through the override chain. (That’s effectively what super means: call the nearest override before me in the linearization order.) So basically, it’s a different way of thinking about the same fact.
That’s an interesting mix of edge-cases you found there.
I’m inclined to agree that the override isn’t allowed according to the spec:
If an override modifier is given, there must be at least one overridden member definition or declaration (either concrete or abstract).
but
An abstract member of a class C is any abstract definition M in some class Ci ∈ L(C)
where L(C) is the linearization of C.
The selftype is not part of the linearization of C, and therefore the members of the selftype are not a member of C, so the member shouldn’t be allowed by the spec to have override.
There are a couple of other interesting tidbits here too: your class is not abstract, but still can’t be instantiated. That is surprising to me. I would have expected the class to be required to be abstract, but in this case, the spec is on the side of the implementation.
Also, if play in IA had a concrete implementation, new B with IA fails to compile! The override modifier doesn’t actually make it override anything.
Use of override here should IMO not be allowed. In the grand scheme of things I don’t think it matters much – the compiler doesn’t allow you to do anything unsafe in either case here, nor does it mislead you to think something is safe that is not. You could deprecate it in dotty, and then maybe drop it in 3.1, though there is 2-3 cross-compilation to think of as well. Whether that’s all worth the hassle is questionable.