Reputation: 643
I had a very hard time to find this bug where it seems field pos of class Icon hides class Element's pos field, only within the draw function.
case class Vector2(val x: Float, val y: Float) { ... }
abstract class Element(var pos: Vector2) {
def draw(): Unit
}
class Icon(pos: Vector2, var texture: String) extends Element(pos) {
override def draw() {
...
GL11.glTranslatef(pos.x, pos.y, 0f)
...
}
}
Later on:
// Create an icon with an initial position
val icon = new Icon(pos = Vector2(40,20), "crosshair")
// Draw all elements
elements.foreach{_.draw()} // => draws icon at (40,20)
// Setting a new position for icon
icon.pos = Vector2(100,200)
// See if it worked
Log.info(icon.pos.toString()) // => this prints Vector2(100,200)
// Draw all elements
elements.foreach{_.draw()} // => still draws icon at (40,20)
I've seen this post and I've tried:
What's the way out ?
Upvotes: 1
Views: 1298
Reputation: 32739
Just explicitly dereference this
:
class Icon(pos: Vector2, var texture: String) extends Element(pos) {
override def draw() {
...
GL11.glTranslatef(this.pos.x, this.pos.y, 0f)
...
}
}
Given that the shadowing only happens inside Icon
, everywhere else (included in derived classes) you can keep using just pos
(no need for this.pos
).
UPDATE: No wait, this does not work! I'd call that a compiler bug. It seems that this.pos
is treated as just pos
even though they should not (IMHO) be the same thing.
There is a simple workaround though:
class Icon(pos: Vector2) extends Element(pos) {
private def self = this
override def draw() {
println(self.pos.x, self.pos.y, 0f)
}
}
UPDATE 2: This is a reply to a comment, which would not fit in another comment.
Randall Schulz says:
I don't believe it's a bug.
Well it certainly looks like it is a bug, or at the very least an inconsistentcy for which I'd like to have a rationale.
The first thing to note is that in my work around above, self eq this
. They really point to the very same reference, and in addition have the same static type. Then how come that self.pos
and this.pos
return two different things (regardless of what the "right" thing to return is)? In other word, self
is an alias to this
, and aliases should necessarily all behave the same.
Now, the reason why I think that this.pos
should denote the variable in Element
and not the parameter to Icon
's constructor is simple. This parameter is not preceded with val
and is thus really a mere parameter (not a val
). As such it is accessible in class Icon
only because of lexical scoping. It is not a member of Icon
, not even a private one (the fact that under the hood a private field is generated does not change the semantics of the language). And if the pos
parameter is not a member there is no reason that this.pos
should return it.
Obviously, this argument boils down to whether or not the parameter is also a member of the class. To me it clearly is not, but if in fact it supposed to automatically also be a member (I'm still looking in the spec for any mention of this) then it is indeed logical that self.pos
returns the value of the parameter instead of the current value of the var in the base class (this still would not explain how self.pos
and this.pos
can mean a different thing though).
That's one of the reason self-types (the (usually) unconstrained variety that use a name other than this) exist.
Well, no. self-types are irrelevant. An unconstrained self-type just introduce an alias, so the alias points to the same reference as this
(and if unconstrained, has the same static type). So using the alias should very much not change what is returned.
And actually, it does not:
class Icon(pos: Vector2) extends Element(pos) { self =>
override def draw() {
println(self.pos.x, self.pos.y, 0f)
}
}
val icon = new Icon(Vector2(1, 2))
icon.draw() // prints "(1.0,2.0,0.0)" as expected
icon.pos = Vector2(3, 4)
icon.draw() // oops, still prints "(1.0,2.0,0.0)"!
As you can see the self type did not help: self.pos
still points to the parameter instead of the variable.
To fix this you might be tempted to try to explictly type self
as Element
:
class Icon(pos: Vector2) extends Element(pos) { self: Element =>
But it does not change anything.
Upvotes: 2
Reputation: 297305
Constructor parameters are visible in a class body, that's just how things work. It is weird to have a private val shadow a public var, I grant you, but that's that. In the particular snippet you showed, you can do this:
abstract class Element {
def pos: Vector2
def pos_=(x: Vector2): Unit
def draw(): Unit
}
and then
class Icon(var pos: Vector2, var texture: String) extends Element {
override def draw() {
...
GL11.glTranslatef(pos.x, pos.y, 0f)
...
}
}
But it won't help if you just want to initialize Element
with a value, instead of declaring a var
on whatever is extending Element
. My own advice is to avoid var
on constructors, use something like initialPos
, and initialize the var
in the body.
Upvotes: 0