erdeszt
erdeszt

Reputation: 791

Scala implicit specialization

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.

Upvotes: 0

Views: 125

Answers (1)

erdeszt
erdeszt

Reputation: 791

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
}

Upvotes: 1

Related Questions