What is the right way to implement recursive calls inside a shapeless typeclass method?
(An early caveat: I'm learning shapeless, so there may be obvious answers/alternatives I do not yet know. Any help is greatly appreciated!)
I have a typeclass which converts a case class into a nested structure of other objects—similar to and inspired by the ToMapRec
example mentioned in this stackoverflow question—except that instead of returning a potentially-recursive Map, it returns a case class composed of potentially recursive members. So instead of converting an instance of MyType
:
trait GetsConverted
case class MyType(someData: String, one: GetsConverted, other: GetsConverted) extends GetsConverted
case class MyOtherType(blah: AndSoOn) extends GetsConverted
case class AndSoOn(eventualFinalValue: Int)
into a possibly recursive/nested Map[String,Any]
(as in the other question), it returns something like an instance of:
case class ReturnType(name: String, data: Option[Any], more: Set[ReturnType])
To create the more
member seems to require making a recursive call inside the type class. But calling the conversion method of the type class inside of another method requires threading in the implicit parameters for all types inside that function to the outermost call. So instead of a typeclass conversion like:
implicit def hconsToMapRec0[K, V, A <: HList, B <: HList](implicit
wit: Witness.Aux[K],
gen: LabelledGeneric.Aux[V, R],
tmrH: Lazy[ToMapRec[A]],
tmrT: Lazy[ToMapRec[B]]
): ReturnType = ???
a depth three method (I'm guessing) would require a function signature something like:
implicit def hconsToMapRec0[K, V, A <: HList, B <: HList, W, C <: HList, D <: HList, X, E <: HList, F <: HList](implicit
wit: Witness.Aux[K],
gen0: LabelledGeneric.Aux[V, A],
tmrH0: Lazy[ToMapRec[A]],
tmrT0: Lazy[ToMapRec[B]],
gen1: LabelledGeneric.Aux[W, C],
tmrH1: Lazy[ToMapRec[C]],
tmrT1: Lazy[ToMapRec[D]],
gen2: LabelledGeneric.Aux[X, E],
tmrH2: Lazy[ToMapRec[E]],
tmrT2: Lazy[ToMapRec[F]]
): ReturnType = ???
Or possibly worse. In general, this approach would require a method which has its implicit parameters multiplied by as many levels of depth in this recursion. And the number of levels of depth is only known at runtime. So this can't be the way to do that.
This feels analogous to the hard-coded 22-arity methods in the scala collections library. Since the raison d'être for Shapeless is to abstract over arity, this seems like a problem calling for more Shapeless-foo that I've learned so far.
So the question is: how would you write a shapeless typeclass to convert an arbitrary case class structured something like the MyType
example above into a recursively defined value like:
ReturnType("MyType", Some("someData"), Set(
ReturnType("MyOtherType", None, Set(
ReturnType("AndSoOn", Some(10), Set())
)),
ReturnType("MyOtherType", None, Set(
ReturnType("AndSoOn", Some(20), Set())
))
))