Last active
June 12, 2020 22:21
-
-
Save pheymann/c3eb42c64e9d2e8bf31dbbe8150580c2 to your computer and use it in GitHub Desktop.
Generic `case class` instance diff's (aka. comparing instance fields and output differences)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import shapeless._ | |
import shapeless.labelled.FieldType | |
import shapeless.record._ | |
trait GenericDiff[H <: HList] { | |
// syntactic sugar | |
type HI = H | |
// compares field values and returns the field name with values if they differ | |
def apply(l: HI, r: HI): Map[String, Any] | |
} | |
trait LowPriorityGenericDiff { | |
implicit val hnilGenDiff: GenericDiff[HNil] = new GenericDiff[HNil] { | |
def apply(l: HNil, r: HNil): Map[String, Any] = | |
Map.empty | |
} | |
implicit def basicGenDiff[K <: Symbol, V, T <: HList](implicit wit: Witness.Aux[K], genDiff: Lazy[GenericDiff[T]]) | |
: GenericDiff[FieldType[K, V] :: T] = | |
new GenericDiff[FieldType[K, V] :: T] { | |
def apply(l: HI, r: HI): Map[String, Any] = { | |
// only report fields which differ | |
// we just use method `equal` here for comparison, we could also provide a typeclass | |
if (l.head != r.head) genDiff.value(l.tail, r.tail) + (wit.value.name -> (l.head, r.head)) | |
else genDiff.value(l.tail, r.tail) | |
} | |
} | |
} | |
trait MediumPriorityGenericDiff extends LowPriorityGenericDiff { | |
implicit def nestedGenDiff[K <: Symbol, V, R <: HList, T <: HList](implicit wit: Witness.Aux[K], | |
gen: LabelledGeneric.Aux[V, R], | |
genDiffH: Lazy[GenericDiff[R]], | |
genDiffT: Lazy[GenericDiff[T]]) | |
: GenericDiff[FieldType[K, V] :: T] = new GenericDiff[FieldType[K, V] :: T] { | |
def apply(l: HI, r: HI): Map[String, Any] = { | |
val diffs = genDiffH.value(gen.to(l.head), gen.to(r.head)) | |
// only report fields which differ | |
if (diffs.isEmpty) genDiffT.value(l.tail, r.tail) | |
else genDiffT.value(l.tail, r.tail) + (wit.value.name -> diffs) | |
} | |
} | |
} | |
trait HighPriorityGenericDiff extends MediumPriorityGenericDiff { | |
implicit def seqGenDiff[K <: Symbol, V, R <: HList, T <: HList](implicit wit: Witness.Aux[K], | |
gen: LabelledGeneric.Aux[V, R], | |
genDiffH: Lazy[GenericDiff[R]], | |
genDiffT: Lazy[GenericDiff[T]]) | |
: GenericDiff[FieldType[K, Seq[V]] :: T] = new GenericDiff[FieldType[K, Seq[V]] :: T] { | |
def apply(l: HI, r: HI): Map[String, Any] = { | |
// we expect elements of `l` and `r` to be in the same order | |
val diffs = l.head.zip(r.head) | |
.map { case (valueL, valueR) => genDiffH.value(gen.to(valueL), gen.to(valueR)) } | |
.filter(_.nonEmpty) | |
// only report fields which differ | |
if (diffs.isEmpty) genDiffT.value(l.tail, r.tail) | |
else genDiffT.value(l.tail, r.tail) + (wit.value.name -> diffs) | |
} | |
} | |
/* add instances for other data structures here, eg. Tree, Map, Vector, ... */ | |
} | |
object Test extends HighPriorityGenericDiff { | |
final case class User(name: String, age: Int) | |
final case class Group(id: Long, users: Seq[User]) | |
// helper | |
def diff[A, H <: HList](l: A, r: A)(implicit gen: LabelledGeneric.Aux[A, H], | |
genDiff: Lazy[GenericDiff[H]]): Map[String, Any] = | |
genDiff.value(gen.to(l), gen.to(r)) | |
def main(args: Array[String]): Unit = { | |
val usr0 = User("foo", 1) | |
val usr1 = User("bar", 2) | |
val usr2 = User("bar", 3) | |
println("equal users ", diff(usr0, usr0)) // (equal users: ,Map()) | |
println("unequal users ", diff(usr0, usr1)) // (unequal users: ,Map(age -> (1,2), name -> (foo,bar))) | |
println("unequal group ", diff(Group(0l, Seq(usr0, usr1)), Group(0l, Seq(usr0, usr2)))) // (unequal group: ,Map(users -> List(Map(age -> (2,3))))) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, trying to use this example, am getting an error like
could not find Lazy implicit value of type GenericDiff[H]
Do you know what might be going wrong. Thanks