0

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.

2
  • Ideally, instead of editing in that you solved your issue, you could just submit an answer to your own question. Just so it's easier to see that the question has been answered. Commented Jun 5, 2017 at 19:03
  • I'll post it as an answer for better visibility, thanks for heads up. Commented Jun 6, 2017 at 7:47

1 Answer 1

1

Solved the issue by making the type variable of ConvertsTo contravariant, scalac now picks the specialized implicit value.

trait ConvertsTo[-A] { def convertTo(value: A): ConversionTree } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.