I've been trying ot create a function that does some inflection on any case class but can't figure out how to pull it off on non concrete generic types, what am I doing wrong?
Please note I can't change the original case classes passed to this function ** edit ** forgot to mention I'm using pre 2.13 scala and prefer a solution that doesn't rely on external libraries.
def caseClassToArray[A](something: A) = {
classOf[A]
.getDeclaredFields
.map { f: Field =>
f.setAccessible(true)
val res = (f.getName, f.get(something))
f.setAccessible(false)
res
}
}
Given what was discussed in comments, it seems the best solution would be a Diff
typeclass and using Shapeless for the derivation for case classes.
The first step is to define the typeclass.
trait Diff[A] {
def diff(a1: A, a2: A): A
}
You may need to adjust the definition for your exact use case.
The next step is to provide support for basic std types like Int
, String
, List[A]
for any A
that has a Diff
, etc
object Diff {
implicit final val IntDiff: Diff[Int] =
new Diff[Int] {
override def diff(i1: Int, i2: Int): Int =
i1 - i2
}
...
}
You may also want to add extension methods,
implicitNotFound
error message, and everything just like any other typeclass.
That stuff is out of the scope of this answer.
The next step is to use Shapeless to inductively derive an instance for any HList
for whose all element types have a Diff
associated.
import shapeless.{:: => :!:, HList, HNil}
sealed trait ReprDiff[R <: HList] extends Diff[R]
object ReprDiff {
implicit final val HNilReprDiff: ReprDiff[HNil] =
new ReprDiff[HNil] {
override final def diff(nil1: HNil, nil2: HNil): HNil = HNil
}
implicit def HConsReprDiff[H, T <: HList](
implicit ev: Diff[H], tailReprDiff: ReprDiff[T]
): ReprDiff[H :!: T] = new ReprDiff[H :!: T] {
override final def diff(hlist1: H :!: T, hlist2: H :!: T): H :!: T = {
val headDiff = ev.diff(hlist1.head, hlist2.head)
val tailDiff = tailReprDiff.diff(hlist1.tail, hlist2.tail)
headDiff :: tailDiff
}
}
}
Finally, using Shapeless Generic
magic we can provide an instance for any case class for which exist an instance of its HList
representation.
import shapeless.Generic
sealed trait DerivedDiff[P <: Product] extends Diff[P]
object DerivedDiff {
implicit def instance[P <: Product, R <: HList](
implicit gen: Generic.Aux[P, R], ev: ReprDiff[R]
): DerivedDiff[P] = new DerivedDiff[P] {
override final def diff(p1: P, p2: P): P =
gen.from(ev.diff(gen.to(p1), gen.to(p2)))
}
}
gen.to
turns any instance of a case classP
into anHList
that represents it.
gen.from
turns a matching instance of anHList
into aP
Now, all we need is a way to let our users derive the instance for their case classes as long as all fields have a Diff
associated with them.
I personally prefer the semiauto
approach:
// Somewhere, like in the companion object of Diff
def derive[P <: Product](implicit ev: Deriving.DerivedDiff[P]): ev.type = ev
With all that, you can just use it like this:
final case class Foo(a: Int, b: Int)
object Foo {
implicit final val FooDiff: Diff[Foo] =
Diff.derive[Foo]
}
Congratulations, you now can diff
two Foo
instances:
val foo1 = Foo(1, 3)
val foo2 = Foo(0, 5)
val diff = Diff.diff(foo1, foo2)
// res: Foo = Foo(1, -2)
In this example we added an auxiliary method
diff
in theDiff
companion object; you may provide any other API to call thediff
operation.
You can see the code running here.