Working through these posts had me thinking I understood self-types, at least somewhat.
So I created an example which failed as expected:
scala> trait A { val v = "a" }
defined trait A
scala> trait B { this :A => ; var v = "" ; this.v = "b" }
<console>:6: error: reassignment to val
trait B { this :A => ; var v = "" ; this.v = "b" }
^
The self-type's "this" shadows B's "this" -- it looks weird, but makes sense.
It would seem wise, then, to give the self-type a different name. I did this and was rather surprised:
scala> trait C { a :A => ; var v = "" ; this.v = "c" }
<console>:6: error: reassignment to val
trait C { a :A => ; var v = "" ; this.v = "c" }
^
It's still shadowed???
Changing the name of the 'local' variable let things compile, which makes sense:
scala> trait D { a :A => ; var w = "" ; this.w = a.v }
defined trait D
(And the self-type name can optionally be used to clarify which "v" to use.)
Okay. Which means that the following should fail?
scala> trait E { this :A => ; var w = "" ; this.w = this.v }
defined trait E
Huh? Which this is this? :-(
So... is there a point to naming self-types? "this" seems to end up shadowed regardless.
Or is this an edge-case of scoping rules, where the self-type's "this" takes precedence over the trait's "this" -- and one should just avoid using the same names for things in related traits?
Your problem is not the name of the self type (in all your examples both this
and the alternate self-type name refer to the very same thing and have the same type, namely ‘the greatest lower bound of B
and A
’ [§5.1, Scala Language Spec]) but that you try to define a field with the same name again without explicitly overriding it.
Look at the simpler example:
trait A { val v = "a" }
trait B { this: A =>
var v = "b"
}
new A with B {} // does not compile
<console>:9: error: overriding value v in trait A$class of type java.lang.String;
variable v in trait B$class of type java.lang.String needs `override' modifier
new A with B {}
So, even though, you don’t get an error in defining B
, you’re simply not able to use it anyway.
This would work
trait A { val v = "a" }
trait B { this:A => override val v = "b" }
new A with B {}
Here, you are explicitly overriding A
’s v
in B
. (Note that new B with A {}
would fail because B
needs to come last.) Also, it has to be a val
because in most cases you cannot really override var
s and you cannot override something else using a var
.
Generally, you should not be concerned about the name of the self-type in these simple cases. As long as you do not create another trait or class inside B
, both this
and whatever you’d call your self-type variable will point to the same thing. There’ll be no shadowing. If you had a new trait inside B
, and you needed to refer to the instance of B
inside that trait, you’d need another name for your self-type.
Consider this
trait B { this =>
val v = "b"
trait C {
val v = "c"
println(this.v)
}
new C {}
}
new B {}
// prints c
vs this:
trait B { self =>
val v = "b"
trait C {
val v = "c"
println(this.v) // prints c
println(self.v) // prints b
}
new C {}
}
new B {}
(Without any further type annotation, you could leave out all of the this
in this example.)