Is it possible to update the fields of any case class that implements a common feature

Suppose we have a common feature model.

trait Model { def id: String def updated: Date } 

And we have 2 case classes extending this attribute.

 case class C1(id: String, updated: Date, foo: String) extends Model case class C2(id: String, updated: Date, bar: Int) extends Model 

Is it possible to write a utility function, as shown below, that takes a Model as parameter and returns a copy with an updated value for the updated field?

 object Model { def update[T <: Model](model: T): T = { model.copy(updated = new Date) // This code does not compile. } } 
+6
source share
3 answers

There are two problems in your code:

  • copy not defined in the tag, so you need to have something specific in the tag that you can use.
  • For update return T instead of Model , each Model must know its actual subtype.

You can fix it as follows:

 trait Model[T <: Model[T]] { def id: String def updated: Date def withDate(d: Date): T } case class C1(id: String, updated: Date, foo: String) extends Model[C1] { def withDate(d: Date) = copy(updated = d) } case class C2(id: String, updated: Date, bar: Int) extends Model[C2] { def withDate(d: Date) = copy(updated = d) } object Model { def update[T <: Model[T]](model: T): T = { model.withDate(new Date) // This code does not compile. } } 

So now it works:

 scala> val c1 = C1("test", new Date, "foo") c1: C1 = C1(test,Mon Apr 21 10:25:10 CDT 2014,foo) scala> Model.update(c1) res0: C1 = C1(test,Mon Apr 21 10:25:17 CDT 2014,foo) 
+3
source

The β€œbest” abstraction you can write here is Lens , which looks like this:

 trait Lens[A, B]{ def get: A => B def set: (A, B) => A } 

so that your code looks like this:

 def update[A](that: A, value: Date)(implicit tLens: Lens[A, Date]): A = tLens set (that, value) 
+3
source

copy is the method defined in your case classes. Not on the basis of Model . What if you have this:

 trait Model { def id: String def updated: Date } case class C1(id: String, updated: Date, foo: String) extends Model case class C2(id: String, updated: Date, bar: Int) extends Model class NotACaseClass(val id: String, val updated: Date) extends Model 

NotACaseClass is a very valid child of Model , and you can pass it an instance of your update function, but good luck finding a way to copy :)

+1
source

All Articles