I'm working on a generic conversion library and I'd like to add automatic typeclass derivation using shapeless. It works in the general case but I'd like to introduce Haskellesque newtypes with automatic unwrapping and so I'd like to specialize the deriving function for my NewType type but scalac still picks up the more generic implicit value. Here's the code so far:
import shapeless._, shapeless.syntax._ trait NewType[A] { val value: A } sealed trait ConversionTree case class CInt(value: Int) extends ConversionTree case class CBoolean(value: Boolean) extends ConversionTree case class CString(value: String) extends ConversionTree case class CArray(value: List[ConversionTree]) extends ConversionTree case class CObject(values: Map[String, ConversionTree]) extends ConversionTree def mergeCObjects(o1: CObject, o2: CObject): CObject = CObject(o1.values ++ o2.values) trait ConvertsTo[A] { def convertTo(value: A): ConversionTree } object ConvertsTo { implicit val intConverter = new ConvertsTo[Int] { ... } implicit val boolConverter = new ConvertsTo[Boolean] { ... } implicit val stringConverter = new ConvertsTo[String] { ... } implicit def arrayConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[List[A]] { ... } implicit def objectConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[Map[String, A]] { ... } implicit def newTypeConverter[A](implicit convertInner: ConvertsTo[A]): ConvertsTo[NewType[A]] = new ConvertsTo[NewType[A]] { override def convertTo(value: NewType[A]): ConversionTree = { convertInner.convertTo(value.value) } } implicit def deriveHNil: ConvertsTo[HNil] = new ConvertsTo[HNil] { override def convertTo(value: HNil): ConversionTree = CObject(Map[String, ConversionTree]()) } // This is the generic case implicit def deriveHCons[K <: Symbol, V, T <: HList]( implicit key: Witness.Aux[K], sv: Lazy[ConvertsTo[V]], st: Lazy[ConvertsTo[T]] ): ConvertsTo[FieldType[K, V] :: T] = new ConvertsTo[FieldType[K, V] :: T]{ override def convertTo(value: FieldType[K, V] :: T): ConversionTree = { val head = sv.value.convertTo(value.head) val tail = st.value.convertTo(value.tail) mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject]) } } // This is the special case for NewTypes implicit def deriveHConsNewType[K <: Symbol, V, T <: HList]( implicit key: Witness.Aux[K], sv: Lazy[ConvertsTo[V]], st: Lazy[ConvertsTo[T]] ): ConvertsTo[FieldType[K, NewType[V]] :: T] = new ConvertsTo[FieldType[K, NewType[V]] :: T] { override def convertTo(value: FieldType[K, NewType[V]] :: T): ConversionTree = { val head = sv.value.convertTo(value.head.value) val tail = st.value.convertTo(value.tail) mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject]) } } implicit def deriveInstance[F, G](implicit gen: LabelledGeneric.Aux[F, G], sg: Lazy[ConvertsTo[G]]): ConvertsTo[F] = { new ConvertsTo[F] { override def convertTo(value: F): ConversionTree = sg.value.convertTo(gen.to(value)) } } } def convertToG[A](value: A)(implicit converter: ConvertsTo[A]): ConversionTree = converter.convertTo(value) case class Id(value: String) extends NewType[String] case class User(id: Id, name: String) println(s"${ConvertsTo.newTypeConverter[String].convertTo(Id("id1"))}") println(s"User: ${convertToG(User(Id("id1"), "user1"))}") Output of the first println: CString("id1")
Output of the second println: CObject(Map("id" -> CObject(Map("value" -> CString("id1"))), "name" -> CString("user1")))
I'd like to get rid of the extra CObject around the id field in the second println. As you can see, calling the newTypeConverter directly results in the correct output but it doesn't work when the NewType is embedded in an object(and if I put a breakpoint in the deriveHConsNewType.convertTo method I can verify that it doesn't get called). I've tried to define deriveHConsNewType like this as well but it didn't help:
implicit def deriveHConsNewType[K <: Symbol, V, N <: NewType[V], T <: HList]( implicit key: Witness.Aux[K], sv: Lazy[ConvertsTo[V]], st: Lazy[ConvertsTo[T]] ): ConvertsTo[FieldType[K, N] :: T] = new ConvertsTo[FieldType[K, N] :: T] { ... } Can someone explain me how the implicit search works when this kind of overlap occurs and provide a solution to my problem?
EDIT: Solved the issue by making the type variable of ConvertsTo contravariant, scalac now picks the specialized implicit value.