Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can auxiliary constructors in Scala only consist of a single call to another constructor?

Tags:

scala

I think I see the merit in defining auxiliary constructors in such a way that the primary constructor is the solitary point of entry to the class. But why can't I do something like this?

class Wibble(foo: Int, bar: String) {
  def this(baz: List[Any]) = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    this(bazLength, someText)
  }
}

Is it maybe a way of guaranteeing that the auxiliary constructor doesn't have side effects and/or can't return early?

like image 887
Phil Darnowsky Avatar asked Jan 08 '13 03:01

Phil Darnowsky


1 Answers

Auxiliary constructors can contain more than a single invocation of another constructor, but their first statement must be said invocation.

As explained in Programming in Scala, ch. 6.7:

In Scala, every auxiliary constructor must invoke another constructor of the same class as its first action. In other words, the first statement in every auxiliary constructor in every Scala class will have the form this(. . . ). The invoked constructor is either the primary constructor (as in the Rational example), or another auxiliary constructor that comes textually before the calling constructor. The net effect of this rule is that every constructor invocation in Scala will end up eventually calling the primary constructor of the class. The primary constructor is thus the single point of entry of a class.

If you’re familiar with Java, you may wonder why Scala’s rules for constructors are a bit more restrictive than Java’s. In Java, a constructor must either invoke another constructor of the same class, or directly invoke a constructor of the superclass, as its first action. In a Scala class, only the primary constructor can invoke a superclass constructor. The increased restriction in Scala is really a design trade-off that needed to be paid in exchange for the greater conciseness and simplicity of Scala’s constructors compared to Java’s.

Just as in Java, one can get round this limitation by extracting the code to be executed before the primary constructor call into a separate method. In Scala it is a bit more tricky than in Java, as apparently you need to move this helper method into the companion object in order to be allowed to call it from the constructor.

Moreover, your specific case is awkward as you have two constructor parameters and - although one can return tuples from a function - this returned tuple is then not accepted as the argument list to the primary constructor. For ordinary functions, you can use tupled, but alas, this doesn't seem to work for constructors. A workaround would be to add yet another auxiliary constructor:

object Wibble {

  private def init(baz: List[Any]): (Int, String) = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    println("init")
    (bazLength, someText)
  }
}

class Wibble(foo: Int, bar: String) {

  println("Wibble wobble")

  def this(t: (Int, String)) = {
    this(t._1, t._2)
    println("You can execute more code here")
  }

  def this(baz: List[Any]) = {
    this(Wibble.init(baz))
    println("You can also execute some code here")
  }

}

This at least works, even if it is slightly complicated.

scala> val w = new Wibble(List(1, 2, 3))

init
Wibble wobble
You can execute more code here
You can also execute some code here
w: Wibble = Wibble@b6e385

Update

As @sschaef's pointed out in his comment, this can be simplified using a factory method in the companion object:

object Wobble {

  def apply(baz: List[Any]): Wobble = {
    val bazLength = baz.length
    val someText = "baz length is " ++ bazLength.toString
    println("init")
    new Wobble(bazLength, someText)
  }
}

class Wobble(foo: Int, bar: String) {
  println("Wobble wibble")
}

Thus we need no new to create an object anymore:

scala>  val w = Wobble(List(1, 2, 3))

init
Wobble wibble
w: Wobble = Wobble@47c130
like image 95
Péter Török Avatar answered Sep 16 '22 22:09

Péter Török