0

Assume the following pair of classes:

class A(arg:String)

class B(argList:Vector[String]) extends A(argList.first)

I want to be able to check for argList being empty before providing the base class constructor with its first element. Unfortunately, placing that check in the default constructor for B (e.g through require, as shown here) is way too late, since the base class' constructor will need to be called first.

This is probably a more general OOP question, but the solution is likely to be Scala-specific.

Community
  • 1
  • 1
Jason
  • 2,495
  • 4
  • 26
  • 37
  • In java, doing anything in a constructor before calling the super constructor gives a compile-time error. In other words, the super constructor **must** be the first statement in a child constructor (it's called implicitly if omitted). I don't think there's a workaround for that in Scala. It's a Java design decision. – Zoltán Nov 02 '15 at 11:26

3 Answers3

4

What do you expect to pass if argList is empty? In any case, you could just use the following:

class B(argList:Vector[String]) extends A(argList.headOption.getOrElse("your default string here")
Zoltán
  • 21,321
  • 14
  • 93
  • 134
  • The essence of both this and @Shadowlands' solutions is the `headOption` method, of which I did not know. Thank you both for the input. Gonna accept this purely for tiebreaking reasons. – Jason Nov 02 '15 at 11:39
4

One way to deal with this is via a companion object. You can mark the constructor for B as private to ensure no-one can by-pass the check, then add a suitable apply method to the companion object that pre-checks the input value(s):

class A(arg:String)

class B private(argList:Vector[String]) extends A(argList.head)

object B {
  def apply(argList:Vector[String]): B = argList.headOption.map(_ => new B(argList)).getOrElse(throw new RuntimeException("Oops"))
}

Usage examples:

scala> B(Vector("foo", "bar"))
res2: B = B@328e9109

scala> B(Vector())
java.lang.RuntimeException: Oops
    at B$$anonfun$apply$2.apply(<console>:24)
    ...

Note that for simplicity's sake, I simply throw an exception when handling bad data, but would probably try some other way of handling this situation (a default value per @Zoltan's answer is one such way).

Shadowlands
  • 14,994
  • 4
  • 45
  • 43
1

That's why in most places constructors are replaced by factory objects. In Scala it's idiomatic to use companion object as such factories.

class A(arg: String)

abstract class B(arg: String) extends A(arg) {
  def argList: IndexedSeq[String]
}

object B {
  case object Empty extends B("") {
    def argList = IndexedSeq.empty
  }
  case class NonEmpty private[B](argList: Vector[String]) extends B(argList.head)

  def apply(argList: Vector[String]) =
    if (argList.isEmpty) Empty else NonEmpty(argList)

  def unapplySeq(b:B): Option[IndexedSeq[String]] = b match {
    case Empty ⇒ Some(IndexedSeq.empty)
    case NonEmpty(args) ⇒ Some(args)
  }
}

you could verify that

B(Vector()) == B.Empty

B(Vector("x", "y")).isInstanceOf[B.NonEmpty]
Odomontois
  • 15,918
  • 2
  • 36
  • 71