Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to update fields of any case class implementing a common trait

Let's assume that we have a common trait Model.

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

And we have 2 case classes extending this trait.

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 like below which 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.
    }
}
like image 730
fcs Avatar asked Apr 21 '14 13:04

fcs


3 Answers

The "best" abstraction you can write here would be a Lens which looks like:

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

so that your code looks like:

def update[A](that: A, value: Date)(implicit tLens: Lens[A, Date]): A =
  tLens set (that, value)
like image 127
wheaties Avatar answered Oct 22 '22 05:10

wheaties


Your code has two issues:

  1. copy is not defined on the trait, so you need to have something defined on the trait that you can use.
  2. In order for update to return a T instead of a Model, every Model must know its actual subtype.

You can fix it like this:

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)
like image 39
dhg Avatar answered Oct 22 '22 05:10

dhg


copy is a method defined on your case classes. Not on your base trait Model. What if you have that:

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 could pass an instance of it to your update function, but good luck finding a copy method :)

like image 38
vptheron Avatar answered Oct 22 '22 04:10

vptheron