morristech
4/5/2019 - 12:08 AM

generic diff of case class instances

generic diff of case class instances

import shapeless._
import shapeless.labelled.FieldType

object ClassDiff extends App {

  sealed abstract class Field(name: String)
  case class FieldSame(name: String) extends Field(name)
  case class FieldDiff[A](name: String, before: A, after: A) extends Field(name)

  trait GenericDiff[HL <: HList] {
    def apply(left: HL, right: HL): Seq[Field]
  }

  implicit val hnilDiff: GenericDiff[HNil] = (_, _) => Nil

  implicit def hlistDiff[S <: Symbol, H, T <: HList](
    implicit wit: Witness.Aux[S],
    gen: Lazy[GenericDiff[T]]
  ): GenericDiff[FieldType[S, H] :: T] = {
    (left, right) => { // SAM-pattern
      if (left.head == right.head) FieldSame(wit.value.name) +: gen.value.apply(left.tail, right.tail)
      else { FieldDiff(wit.value.name, left.head, right.head) +: gen.value.apply(left.tail, right.tail) }
    }
  }

  def diff[A, HL <: HList](left: A, right: A)(
    implicit G: LabelledGeneric.Aux[A, HL], gen: Lazy[GenericDiff[HL]]) = {
    gen.value.apply(G.to(left), G.to(right))
  }

  case class User(id: Long, name: String, age: Int)

  println(diff(User(1L, "alice", 20), User(2L, "alice", 35)))

  case class GroupName(value: String)
  case class Group(id: Long, name: GroupName)

  println(diff(Group(1, GroupName("tech")), Group(2L, GroupName("hoge"))))
}