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.
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
Ich werde es als eine Antwort für bessere Sichtbarkeit posten, danke für den Kopf. – erdeszt