Home > database >  Scala - copy case class with dynamic fields. Is it possible?
Scala - copy case class with dynamic fields. Is it possible?

Time:12-30

Considering a case class that has a default field calculated at creating time, is it possible to copy it where the new copied case class has this field recalculated?

For example, TestCopy.auto is generated at creation time:

  case class TestCopy(static: String, auto: Long = System.currentTimeMillis()) {
    val auto2 = System.currentTimeMillis()
  }

If I copy it changing only the field static:

    val a = TestCopy(static = "a")
    val b = a.copy(static = "b")

The field auto is not recalculated. Neither is the constructor field auto2:

> println(a)
> println(b)
> println(a.auto2)
> println(b.auto2)

TestCopy(a,1640773176392)
TestCopy(b,1640773176392)
1640773176392
1640773176392

It makes sense, actually, since I am copying the case class. However, I wanted a behaviour similar to recreating the case class, where I change only a set of static fields. The example above is simplified, I could have just created it again. The question is aiming at cases like:

  case class TestCopy2(static1: String,
                       static2: String,
                       static3: String,
                       auto: Long = System.currentTimeMillis())

Where only static1 must be changed, static2 and static3 must be copied, and auto must be generated again. Is it possible?

Thank you all.

CodePudding user response:

A custom apply method is viable, but you should also be able to leverage the abstract case class trick:

abstract case class TestCopy(static: String, auto: Long = System.currentTimeMillis()) {
  def copy(static: String = static, auto: Long = System.currentTimeMillis()): TestCopy =
    new TestCopy(static, auto)
}

object TestCopy {
  def apply(static: String, auto: Long = System.currentTimeMillis()): TestCopy =
    new TestCopy(static, auto)
}

Making the case class abstract suppresses the auto-generated apply and copy methods. In this case, the auto-generated apply is basically exactly what the compiler would generate. The generated copy would normally be something like

def copy(static: String = static, auto: Long = auto): TestCopy

Note that auto defaults to the previous value, so that's the behavior we want to change, thus we define a custom copy method to replace the compiler-generated one.

One of the defaults for auto can be eliminated by making the constructor private; since case classes are not typically instantiated with new, this isn't much of a loss:

abstract case class TestCopy private (static: String, auto: Long) {
  def copy(static: String = static, auto: Long = System.currentTimeMillis()): TestCopy =
    new TestCopy(static, auto)
}

object TestCopy {
  def apply(static: String, auto: Long = System.currentTimeMillis()): TestCopy =
    new TestCopy(static, auto)
}

Now you can:

  • create with default current time: TestCopy("foo")
  • create with preset time: TestCopy("foo", 0)
  • copy with current time: TestCopy("foo").copy(static = "bar")
  • copy with preset time: TestCopy("foo").copy(auto = 0)

CodePudding user response:

Transient values don't get copied automatically:

case class Foo(a: Int) { 
   @transient val b: Long = System.currentTimeMillis 
}


val f1 = Foo(1)
Thread.sleep(1000)
val f2 = f1.copy(2)

assert f1.b != f2.b
  • Related