Skip to content

Instantly share code, notes, and snippets.

@petitviolet
Created November 17, 2018 13:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save petitviolet/c0ad41d8757d56a42979fdcee22838d9 to your computer and use it in GitHub Desktop.
Save petitviolet/c0ad41d8757d56a42979fdcee22838d9 to your computer and use it in GitHub Desktop.
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"))))
}
@petitviolet
Copy link
Author

outputs are below.

List(FieldDiff(id,1,2), FieldSame(name), FieldDiff(age,20,35))
List(FieldDiff(id,1,2), FieldDiff(name,GroupName(tech),GroupName(hoge)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment