0

I would like to declare some auxiliary values inside a case class constructor, but it seems not to be correct Scala.

In short, the following piece of code is correct:

case class Something(
    text1: String,
    text2: String
) {
    def this(datetime: LocalDateTime) {
        this(
            s"date: ${datetime.toLocalDate.toString()}",
            s"time: ${datetime.toLocalTime.toString()}"
        )
    }
}

and the following is not:

case class Something(
    text1: String,
    text2: String
) {
    def this(datetime: LocalDateTime) {
        val date = datetime.toLocalDate.toString()
        val time = datetime.toLocalTime.toString()
        this(
            s"date: $date",
            s"time: $time"
        )
    }
}

even though the latter would be more legible and easier to maintain. (Imagine using more complex operations than just calling two methods.) Why is that?

Is there another way to write a constructor like that or a way to work around this?

M. M.
  • 322
  • 1
  • 10

3 Answers3

2

In Scala first call must be to primary constructor. After that you can have as much code as you want. Read this for explanation.

Similar rule applies to Java for this and super. Not exactly same though. Read this.

The reason why this and super must be first is, that one can set fields to various values before that actual this(x, y) is called. This means object is being constructed and different values can be visible to any thread that may have reference to the object while construction is in progress.

Thanks.

Apurva Singh
  • 4,534
  • 4
  • 33
  • 42
0

Auxiliary constructors have a constraint that it should call a previous auxiliary constructor or primary constructor on the first line of its body. The second code does not follow that rule. Hence the error.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
0

In your second case you are not allowed to define variables inside constructor before this(params) call, as computing inside constructors are discouraged in scala class or case class. One way you can fix it is pass inline constructor params.

  test("test case class custom constructor") {
    case class Something(text1: String,text2: String) {

      def this(datetime: LocalDateTime) {
        this(datetime.toLocalDate.toString(), datetime.toLocalTime.toString())
        //you can do whatever you want after this(x, y) is invoked
        val testVal = "apple"
        println(testVal)
      }
    }

    new Something(LocalDateTime.now()).text1 shouldBe "2017-07-16"
    new Something(LocalDateTime.now()).text2 should not be empty
  }

Another way (Encouraged way) is define case class and then define apply inside a companion object as below (for older version maybe 2.11.8, companion object had to be defined first and only case class which seems to be fixed now - https://issues.scala-lang.org/browse/SI-3772)

  test("test class with companion apply method") {

    case class Something(val text1: String, val text2: String) {}

    object Something {

      def apply(datetime: LocalDateTime): Something = {

        val x = datetime.toLocalDate.toString()
        val y = datetime.toLocalTime.toString()

        new Something(x, y)
      }
    }

    Something(LocalDateTime.now()).text1 shouldBe "2017-07-16"
    Something(LocalDateTime.now()).text2 should not be empty

  }

scastie code - https://scastie.scala-lang.org/prayagupd/yn2bJWHkQ6Gbli5Ll6I6CQ/1

prayagupa
  • 30,204
  • 14
  • 155
  • 192
  • @M.M. all `case classes` already have their companion object. Did you even try? You will compiler error saying the same. – prayagupa Jul 17 '17 at 00:36
  • 1
    Well, I did and I didn't get any error… https://scastie.scala-lang.org/5qp4Ml5tTAG8IWe1Yw4Nng – M. M. Jul 17 '17 at 00:46
  • Oh yeah, you are right works with `scala 2.12` - https://github.com/scala/scala/pull/2816. At least I had to define companion object first and then case class in some `versions 2.11._`. Will update the answer. Thanks – prayagupa Jul 17 '17 at 01:40