matanox
matanox

Reputation: 13686

Custom and multiple constructor inheritance in Scala?

As I understand the semantics of a custom constructor may be typically added to a class via a companion object. Is there then, any way to inherit a custom constructor while inheriting a class?

On the one hand I have found that companion objects are not synthetically inherited along a case class, and on the other, I am not aware of a way of creating custom constructors inside a class itself, so that they are inherited. And yet inheriting custom constructors seems to be a perfectly valid use case to me. So is it supported in some (straightforward) way in Scala?

A naive demonstration of intent:

class A {}
object A {
  def apply(n: Int) = {
    println(n)
    new A
  }
}

class B extends A {}

object Test {
  val a1 = A
  val a2 = A(3)
  val b1 = B    // compile error
  val b2 = B(3) // compile error

P.S. I have even found the arcane/deviant technique of defining this custom constructors result in a custom constructor that does not in actuality get inherited (it does work for just creating custom constructors, but quite oddly and unfortunately those do not get inherited). Demonstrating code:

class A {
  def this(n: Int) = {
    this
    println(n)
  }
}

class B extends A {}

object Test {
  val a1: A = new A
  val a2: A = new A(3)
  val b1 = new B    
  val b2 = new B(3) // compile error
}

Clarification of Intent Edit:

consider "constructor" and "companion factory methods" interchangeable for the sake of this question.

Upvotes: 1

Views: 893

Answers (3)

matanox
matanox

Reputation: 13686

Short answer: no straightforward way; try to workaround and resist the desire.

Upvotes: 1

Rex Kerr
Rex Kerr

Reputation: 167891

You can't inherit constructors directly, and because you can't you also can't inherit things that use them without a little bit of work. But you can abstract away anything beyond the constructor call.

Let's suppose we have

class Foo(text: String) {
  override def toString = "Foo: " + text
}
object Foo {
  def apply(text: String) = new Foo(text)  // Auto-generated for case class
  def apply(i: Int) = new Foo(
    if (i > 0) i.toString
    else if (i == 0) ""
    else s"negative ${0L - i}"
  )
}

and we then decide to

class Bar(text: String) extends Foo(text) {
  override def toString = "Bar: " + text
}

Now, what do we do about object Bar? Instead of writing all the logic over again, we create a trait to separate and abstract the object creation from the computation of the constructor parameter(s):

trait FooCompanionLike[A <: Foo] {
  def apply(text: String): A    // I am abstract!
  def apply(i: Int): A = apply(
    if (i > 0) i.toString
    else if (i == 0) ""
    else s"negative ${0L - i}"
  )
}

Now we can

object Foo extends FooCompanionLike[Foo] {
  def apply(text: String) = new Foo(text)
}
object Bar extends FooCompanionLike[Bar] {
  def apply(text: String) = new Bar(text)
}

So you can't completely escape boilerplate, but you can reduce it to extending from a trait and a single method call.

If you do it this way (where the abstract apply perfectly matches the constructor), you can even get case classes to work without manually defining the abstract apply method in the companion:

case class Baz(text: String) extends Foo(text) {
  override def toString = "Baz: " + text
}
object Baz extends FooCompanionLike[Baz] {
  // Nothing here!  Auto-generated apply works!
}

Upvotes: 2

Oli
Oli

Reputation: 1142

Constructors in Scala are defined in the body of the class and take parameters after the class name e.g.

class A(i: Int) {
  println(i)
}

The println(i) in this case is the constructor logic. If you now extend A, like this:

class B(i: Int) extends A(i)

and instantiate B, val b1 = new B(2) you'll see that the constructor is indeed inherited.

As you've already found out, Scala allows you to define alternative constructors by defining functions called this. But these alternative constructors must call the primary constructor.

The way I understand it is that there is really only one constructor for any Scala class, the alternative constructors just filter into it. For example:

class A(x: Int, y: Int) {
  // do some constructing!

  def this(x: Int) = {
    this(x, 1) // provide a default value for y
  }
}

Upvotes: 0

Related Questions