2015-04-20 10 views
11

Ich bin die Umsetzung derzeit eine Bibliothek zur Serialisierung und nach und von XML-RPC-Nachrichten deserialisieren. Es ist fast fertig, aber jetzt versuche ich, die vorformulierten meiner aktuellen asProduct Methode Shapeless Verwendung zu entfernen. Mein aktueller Code:Wie formlose Fallklassen mit Attributen und Typklassen?

trait Serializer[T] { 
    def serialize(value: T): NodeSeq 
} 

trait Deserializer[T] { 
    type Deserialized[T] = Validation[AnyErrors, T] 
    type AnyErrors = NonEmptyList[AnyError] 
    def deserialize(from: NodeSeq): Deserialized[T] 
} 

trait Datatype[T] extends Serializer[T] with Deserializer[T] 

// Example of asProduct, there are 20 more methods like this, from arity 1 to 22 
def asProduct2[S, T1: Datatype, T2: Datatype](apply: (T1, T2) => S)(unapply: S => Product2[T1, T2]) = new Datatype[S] { 
    override def serialize(value: S): NodeSeq = { 
    val params = unapply(value) 
    val b = toXmlrpc(params._1) ++ toXmlrpc(params._2) 
    b.theSeq 
    } 

    // Using scalaz 
    override def deserialize(from: NodeSeq): Deserialized[S] = (
     fromXmlrpc[T1](from(0)) |@| fromXmlrpc[T2](from(1)) 
    ) {apply} 
} 

Mein Ziel ist der Benutzer meiner Bibliothek zu ermöglichen/Klassen deserialize Fall ohne ihn vorformulierten Code zu schreiben zwingen zu serialisiert. Derzeit müssen Sie die Fallklasse und ein implizites val deklarieren, indem Sie die zuvor erwähnte asProduct-Methode verwenden, um eine Datatype-Instanz im Kontext zu haben. Diese implizite wird im folgenden Code verwendet:

def toXmlrpc[T](datatype: T)(implicit serializer: Serializer[T]): NodeSeq = 
    serializer.serialize(datatype) 

def fromXmlrpc[T](value: NodeSeq)(implicit deserializer: Deserializer[T]): Deserialized[T] = 
    deserializer.deserialize(value) 

Dies ist die klassische Strategie der Serialisierung und Deserialisierung mit Typklassen.

In diesem Moment habe ich begriffen, wie von Fall Klassen hList über Generisches oder LabelledGeneric zu konvertieren. Das Problem ist, wenn ich diese Umwandlung getan haben, wie ich die Methoden aufrufen können fromXmlrpc und toXmlrpc wie im asProduct2 Beispiel. Ich habe keine Informationen über die Arten der Attribute in dem Fall, Klasse und daher kann der Compiler keine implizite finden, die fromXmlrpc und toXmlrpc erfüllen. Ich muß einen Weg zu beschränken, dass alle Elemente eines hList Datentyp in Zusammenhang einen impliziten haben.

Da ich einen Anfänger mit Shapeless bin, würde ich gerne wissen, was der beste Weg, um diese Funktionalität zu bekommen. Ich habe einige Einsichten, aber ich habe definitiv keine Ahnung, wie man es mit Shapeless machen kann. Das Ideal wäre, eine Art zu haben, den Typ von einem gegebenen Attribut der Fallklasse zu erhalten und diesen Typ explizit an von Xmlrpc und anXmlrpc zu übergeben. Ich stelle mir vor, dass dies nicht möglich ist.

Antwort

15

Zuerst müssen Sie generische Serialisierer für HList schreiben. Das heißt, müssen Sie angeben, wie die Serialisierung H :: T und HNil:

implicit def hconsDatatype[H, T <: HList](implicit hd: Datatype[H], 
              td: Datatype[T]): Datatype[H :: T] = 
    new Datatype[H :: T] { 
    override def serialize(value: H :: T): NodeSeq = value match { 
     case h :: t => 
     val sh = hd.serialize(h) 
     val st = td.serialize(t) 
     (sh ++ st).theSeq 
    } 

    override def deserialize(from: NodeSeq): Deserialized[H :: T] = 
     (hd.deserialize(from.head) |@| td.deserialize(from.tail)) { 
     (h, t) => h :: t 
     } 
    } 

implicit val hnilDatatype: Datatype[HNil] = 
    new Datatype[HNil] { 
    override def serialize(value: HNil): NodeSeq = NodeSeq() 
    override def deserialize(from: NodeSeq): Deserialized[HNil] = 
     Success(HNil) 
    } 

Dann können Sie einen allgemeinen Serializer für jede Art definieren, die über Generic dekonstruiert werden können:

implicit def genericDatatype[T, R](implicit gen: Generic.Aux[T, R], 
            rd: Lazy[Datatype[R]]): Datatype[T] = 
    new Datatype[T] { 
    override def serialize(value: T): NodeSeq = 
     rd.value.serialize(gen.to(value)) 

    override def deserialize(from: NodeSeq): Deserialized[T] = 
     rd.value.deserialize(from).map(rd.from) 
    } 

Bitte beachte, dass ich musste Verwenden Sie Lazy, da andernfalls dieser Code den impliziten Auflösungsprozess unterbrechen würde, wenn Sie geschachtelte Fallklassen haben. Wenn Sie die Fehlermeldung "divergierende implizite Erweiterung" erhalten, können Sie versuchen, die impliziten Parameter Lazy auch in hconsDatatype und hnilDatatype hinzuzufügen.

Dies funktioniert, weil Generic.Aux[T, R] Links des willkürlichen Produkte artigen Typ T und ein HList Typ R.Zum Beispiel für diesen Fall Klasse

case class A(x: Int, y: String) 

formlos eine Generic Instanz vom Typ

Generic.Aux[A, Int :: String :: HNil] 

Folglich erzeugen wird, können Sie die Serialisierung delegieren können rekursiv definiert Datatype s für HList, um die Daten zu HList mit der Umwandlung Generic zuerst. Die Deserialisierung funktioniert ähnlich, aber umgekehrt - zuerst wird die serialisierte Form in HList eingelesen und dann wird diese HList in die tatsächlichen Daten mit Generic konvertiert.

Es ist möglich, dass ich mehrere Fehler in der Verwendung von NodeSeq API oben gemacht habe, aber ich denke, es vermittelt die allgemeine Idee.

Wenn Sie LabelledGeneric verwenden möchten, würde der Code etwas komplexer werden, und noch mehr, wenn Sie versiegelte Merkmalshierarchien behandeln möchten, die mit Coproduct s dargestellt werden.

Ich benutze formlos, um generische Serialisierungsmechanismus in meiner Bibliothek, picopickle bereitzustellen. Mir ist keine andere Bibliothek bekannt, die das formlos macht. Sie können versuchen, einige Beispiele zu finden, wie formlos in dieser Bibliothek verwendet werden könnte, aber der Code dort ist etwas komplex. Es gibt auch ein Beispiel unter formlosen Beispielen, nämlich S-expressions.

11

Vladimirs Antwort ist großartig und sollte die akzeptierte sein, aber es ist auch möglich, dies ein wenig schöner mit Shapeless's TypeClass machinery zu tun. Angesichts der folgenden Setup:

import scala.xml.NodeSeq 
import scalaz._, Scalaz._ 

trait Serializer[T] { 
    def serialize(value: T): NodeSeq 
} 

trait Deserializer[T] { 
    type Deserialized[T] = Validation[AnyErrors, T] 
    type AnyError = Throwable 
    type AnyErrors = NonEmptyList[AnyError] 
    def deserialize(from: NodeSeq): Deserialized[T] 
} 

trait Datatype[T] extends Serializer[T] with Deserializer[T] 

Wir diesen schreiben können:

import shapeless._ 

object Datatype extends ProductTypeClassCompanion[Datatype] { 
    object typeClass extends ProductTypeClass[Datatype] { 
    def emptyProduct: Datatype[HNil] = new Datatype[HNil] { 
     def serialize(value: HNil): NodeSeq = Nil 
     def deserialize(from: NodeSeq): Deserialized[HNil] = HNil.successNel 
    } 

    def product[H, T <: HList](
     dh: Datatype[H], 
     dt: Datatype[T] 
    ): Datatype[H :: T] = new Datatype[H :: T] { 
     def serialize(value: H :: T): NodeSeq = 
     dh.serialize(value.head) ++ dt.serialize(value.tail) 

     def deserialize(from: NodeSeq): Deserialized[H :: T] = 
     (dh.deserialize(from.head) |@| dt.deserialize(from.tail))(_ :: _) 
    } 

    def project[F, G](
     instance: => Datatype[G], 
     to: F => G, 
     from: G => F 
    ): Datatype[F] = new Datatype[F] { 
     def serialize(value: F): NodeSeq = instance.serialize(to(value)) 
     def deserialize(nodes: NodeSeq): Deserialized[F] = 
     instance.deserialize(nodes).map(from) 
    } 
    } 
} 

Seien Sie sicher, dass diese alle zusammen definieren, so werden sie richtig companioned werden.

Dann, wenn wir einen Fall Klasse haben:

case class Foo(bar: String, baz: String) 

Und Instanzen für die Typen der Fall Klassenmitglieder (in diesem Fall nur String):

implicit object DatatypeString extends Datatype[String] { 
    def serialize(value: String) = <s>{value}</s> 
    def deserialize(from: NodeSeq) = from match { 
    case <s>{value}</s> => value.text.successNel 
    case _ => new RuntimeException("Bad string XML").failureNel 
    } 
} 

Wir erhalten automatisch eine abgeleitete Instanz für Foo:

scala> case class Foo(bar: String, baz: String) 
defined class Foo 

scala> val fooDatatype = implicitly[Datatype[Foo]] 
fooDatatype: Datatype[Foo] = [email protected] 

scala> val xml = fooDatatype.serialize(Foo("AAA", "zzz")) 
xml: scala.xml.NodeSeq = NodeSeq(<s>AAA</s>, <s>zzz</s>) 

scala> fooDatatype.deserialize(xml) 
res1: fooDatatype.Deserialized[Foo] = Success(Foo(AAA,zzz)) 

arbeitet Hier geht es um die sa Ich als Vladimirs Lösung, aber es lässt einige der langweiligen Boilerplate vom Typ Klasseninstanz-Ableitung formlos abstrahieren, so dass man sich mit Generic nicht die Hände schmutzig machen muss.