2017-06-04 3 views
0

Ich arbeite an einer generischen Konvertierungsbibliothek und möchte eine automatische Typklassenableitung mit formlos hinzufügen. Es funktioniert im allgemeinen Fall, aber ich möchte Haskellesque newtypes mit automatischem Auspacken vorstellen und so möchte ich die Ableitungfunktion für meinen NewType Typ spezialisieren, aber scalac greift noch den allgemeineren impliziten Wert auf. Hier ist der Code so weit:Scala implizite Spezialisierung

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"))}") 

Ausgang des ersten println: CString("id1")

Ausgang des zweiten println: CObject(Map("id" -> CObject(Map("value" -> CString("id1"))), "name" -> CString("user1")))

Ich mag würde, um loszuwerden der zusätzlichen CObject um das ID-Feld in der zweiten println. Wie Sie sehen können, führt der Aufruf der newTypeConverter direkt zu der richtigen Ausgabe, aber es funktioniert nicht, wenn die NewType in ein Objekt eingebettet ist (und wenn ich einen Haltepunkt in der deriveHConsNewType.convertTo Methode kann ich überprüfen, dass es nicht aufgerufen wird) . Ich habe versucht, auch deriveHConsNewType so zu definieren, aber es half nicht:

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] { ... } 

mir jemand erklären kann, wie die implizite Suche funktioniert, wenn diese Art von Überlappung auftritt und eine Lösung für mein Problem zur Verfügung stellen?

EDIT: Das Problem gelöst, indem die Typvariable ConvertsTo contravariant, scalac jetzt den spezialisierten impliziten Wert wählt.

+0

Idealerweise können Sie, anstatt das Problem zu lösen, nur eine Antwort auf Ihre eigene Frage einreichen. Nur so ist es einfacher zu sehen, dass die Frage beantwortet wurde. – John

+0

Ich werde es als eine Antwort für bessere Sichtbarkeit posten, danke für den Kopf. – erdeszt

Antwort

1

Das Problem wurde behoben, indem die Typvariable ConvertsTo contravariant erstellt wurde. Scalac wählt nun den spezialisierten impliziten Wert aus.

trait ConvertsTo[-A] { 
    def convertTo(value: A): ConversionTree 
}