Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic Scala way to deal with base vs derived class field names?

Consider the following base and derived classes in Scala:

    abstract class Base( val x : String )

    final class Derived( x : String ) extends Base( "Base's " + x )
    {
        override def toString = x
    }

Here, the identifier 'x' of the Derived class parameter overrides the field of the Base class, so invoking toString like this:

    println( new Derived( "string" ).toString )

returns the Derived value and gives the result "string".

So a reference to the 'x' parameter prompts the compiler to automatically generate a field on Derived, which is served up in the call to toString. This is very convenient usually, but leads to a replication of the field (I'm now storing the field on both Base and Derived), which may be undesirable. To avoid this replication, I can rename the Derived class parameter from 'x' to something else, like '_x':

    abstract class Base( val x : String )

    final class Derived( _x : String ) extends Base( "Base's " + _x )
    {
        override def toString = x
    }

Now a call to toString returns "Base's string", which is what I want. Unfortunately, the code now looks somewhat ugly, and using named parameters to initialize the class also becomes less elegant:

    new Derived( _x = "string" ) 

There is also a risk of forgetting to give the derived classes' initialization parameters different names and inadvertently referring to the wrong field (undesirable since the Base class might actually hold a different value).

Is there a better way?

Edit 1: To clarify, I really only want the Base values; the Derived ones just appear to be necessary for initializing the fields of the base class. The example only references them to illustrate the ensuing issues.

Edit 2: Actually, the example would have been clearer if I had used vars instead of vals, since that highlights the problem with values getting changed later on in the base class:

    class Base( var x : Int ) { def increment() { x = x + 1 } }
    class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }

    val derived = new Derived( 1 )
    println( derived.toString )     // yields '1', as expected
    derived.increment()
    println( derived.toString )     // still '1', probably unexpected

Edit 3: It might be nice to have a way to suppress automatic field generation if the derived class would otherwise end up hiding a base class field. It would appear that the Scala compiler could actually have been designed to do this for you, but of course this contradicts the more general rule of "nearer" identifiers (the Derived class' 'x') hiding more remote ones (the Base class' 'x'). It seems like a reasonably nice solution would be a modifier like 'noval', maybe like this:

    class Base( var x : Int ) { def increment() { x = x + 1 } }
    class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }

    val derived = new Derived( 1 )
    println( derived.toString )     // yields '1', as expected
    derived.increment()
    println( derived.toString )     // still '2', as expected
like image 972
Gregor Scheidt Avatar asked Jun 29 '11 04:06

Gregor Scheidt


3 Answers

The idiomatic way to avoid duplicating the field would be to write

abstract class Base { val x: String }

final class Derived(val x: String) extends Base {
   def toString = x
}

However, in your version it looks like you actually want a second field, since you have two distinct values. As you correctly point out, giving these fields the same name is likely to lead to confusion.

Since you don't actually need the constructor argument outside of the constructor, you could use this approach (a private constructor with a companion module that acts as a factory):

abstract class Base { val x: String }

final class Derived private (val x: String) extends Base {
   def toString = x
}
object Derived {
   def apply(x: String) = new Derived("Base " + x)
}
like image 188
Aaron Novstrup Avatar answered Oct 07 '22 13:10

Aaron Novstrup


As the base class is abstract, it doesn't look as though you really want a val (or var) in the Base class, with the associated backing field. Instead, you're simply looking to make a guarantee that such a thing will be available in concrete subclasses.

In Java, you'd use an accessor method such as getX to achieve this.

In Scala, we can go one better, vals, vars and defs occupy the same namespace, so a val can be used to implement an abstract def (or to override a concrete def, if that's what floats your boat). More formally, this is known as the "Uniform Access Principle"

abstract class Base{ def x: String }

class Derived(val x: String) extends Base {
    override def toString = x
}

If you need for x to be settable via a reference to Base, you also need to declare the "setter" (which can then be implemented with a var):

abstract class Base {
  def x: String
  def x_=(s: String): Unit
}

class Derived(var x: String) extends Base {
    override def toString = x
}

(not that I would ever encourage mutability in any design; unless there was an especially compelling justification for it. There are too many good reasons for favouring immutability by default)

UPDATE

The benefits of this approach are:

  • x could be an entirely synthetic value, implemented entirely in terms of other values (i.e. the area of a circle for which you already know the radius)
  • x can be implemented at any arbitrary depth in the type hierarchy, and doesn't have to be explicitly passed through each intervening constructor (having a different name each time)
  • There's only a single backing field required, so no memory is wasted
  • As it now doesn't need a constructor, Base could be implemented as a trait; if you so desire
like image 25
Kevin Wright Avatar answered Oct 07 '22 12:10

Kevin Wright


You can try this:

abstract class Base( val x : String )

final class Derived( _x : String ) extends Base( _x ) {
  override val x  = "Base's " + _x
  override def toString = x
}

Then

println(new Derived("string").toString)

prints exactly what you want

like image 36
Yuriy Zubarev Avatar answered Oct 07 '22 12:10

Yuriy Zubarev