jross
jross

Reputation: 1119

Redefine a variable type in a subclass (i.e. variable hiding)

I have googled this several different ways… but have yet to find a definitive answer.

class Foo1 { }
class Foo2 extends Foo1 { }

class Bar1 {
    var foo: Foo1 = null
}

class Bar2 extends Bar1 {
    var foo: Foo2 = null  // <-- compiler error
}

The error says: "overriding variable foo in class Bar1 of type test.Foo1; variable foo needs `override' modifier" but you can't override the var either.

In C# you can use the new keyword like this

class Foo1 { }
class Foo2 : Foo1 { }

class Bar1
{
    public Foo1 foo = null;
}

class Bar2 : Bar1 
{
    public new Foo2 foo = null;
}

Is it possible to do this in Scala?

EDIT -- Added another C# example to show why I am doing this.

class Foo1 { }
class Foo2 : Foo1 { }

class Bar1
{
    public Foo1 Foo { get { return _foo; } }
    protected Foo1 _foo;
    public Bar1(Foo1 foo) { _foo = foo; }       
}

class Bar2 : Bar1
{
    public new Foo2 Foo { get { return _foo; } }
    protected new Foo2 _foo;
    public Bar2(Foo2 foo) : base(foo) { _foo = foo; }
}

Of course in the real app the classes are way more complex w/ many members and the hierarchy goes even deeper.

In short what this does for me is that it allows me to use the foo variable anywhere in my class hierarchy w/o having to use an ugly cast – which would be the case if I only had the single foo declared in the Bar1 class.

Thanks!

Upvotes: 1

Views: 253

Answers (2)

som-snytt
som-snytt

Reputation: 39577

This would normally be expressed as a type parameter or type member.

For example, collections track their underlying "representation" or implementation:

https://github.com/scala/scala/blob/v2.11.4/src/library/scala/collection/GenSeqLike.scala#L33

It pops up in places like:

https://github.com/scala/scala/blob/v2.11.4/src/library/scala/collection/LinearSeqOptimized.scala#L27

But the other question addressed by the mechanism of member hiding is a brittle type hierarchy. Classes must be designed for inheritance (Effective Java). So the notion that a class can evolve without regard for compatibility with clients (including subclasses) is misleading.

This came up in Scala because of how private members were inherited (or not). I can't find the discussion offhand, but in this issue it's called the weird "private members aren't inherited" change. You might go, "What's weird about that?"

This issue might be the umbrella issue or meta-bug.

This is the case that annoys people:

scala> class A { val v: Int = 42 }
defined class A

scala> class B extends A { private val v: String = "42" }
<console>:8: error: overriding value v in class A of type Int;
 value v has weaker access privileges; it should not be private
       class B extends A { private val v: String = "42" }
                                       ^

Some have advocated for stronger privacy rights.

Upvotes: 2

Dimitri
Dimitri

Reputation: 1786

It looks like you're looking for covariance on a var attribute. That doesn't compile because it's not sound:

class Foo1
class Foo2 extends Foo1 {
  def moo = 42
}
class Foo3 extends Foo1

class Bar1 {
  var foo: Foo1 = null
}

class Bar2 extends Bar1 {
  var foo: Foo2 = null // Let's suppose this compiles
}    

val bar2: Bar2 = new Bar2
val bar1: Bar1 = bar2
bar1.foo = new Foo3
bar2.foo.moo

On the last line you call .moo on an instance of Foo3 (which doesn't implement such method) but the compiler can't tell you this because bar2.foo is supposed to be a Foo2.

On a val however it compiles:

class Bar1 {
  val foo: Foo1 = new Foo1
}

class Bar2 extends Bar1 {
  override val foo: Foo2 = new Foo2
}

Update

After some tests and reading this, what I understand in your C# code is that you really have 2 fields in an instance of Bar2. When seen as Bar1, x.foo refers to the field from Bar1, and seen as Bar2, x.foo refers to the attribute in Bar2. And their type and even their nature (method / field / type) can be completely unrelated. I can't think of anything similar in Scala. But if I'm correct, you could as well use another name for Bar2's foo.


Update 2

The following code may be what you want, if foo is never reassigned:

class Bar1(val foo: Foo1)

class Bar2(override val foo: Foo2) extends Bar1(foo)

Upvotes: 2

Related Questions