2015-04-20 56 views
11

我目前正在實現一個庫來對XML-RPC消息進行序列化和反序列化。這幾乎完成,但現在我試圖刪除我當前的樣板使用方法無形。我當前的代碼:如何使用屬性和類型類構造不成形的case類?

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

我的目標是讓我的媒體庫的用戶序列化/反序列化的情況下類,而不必強迫他寫的樣板代碼。目前,您必須使用前面提到的asProduct方法來聲明案例類和一個隱式的val,以便在上下文中具有一個Datatype實例。這種隱含在以下代碼中使用:

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) 

這是序列化和使用類型類解串的經典的策略。

在這一刻,我已經掌握瞭如何從案例類HList通過通用LabelledGeneric轉換。問題是一旦我完成了這個轉換,我可以像在asProduct2的例子中一樣,調用方法fromXmlrpctoXmlrpc。我沒有關於案例類中屬性類型的任何信息,因此,編譯器找不到任何隱含的,從Xmlrpc和到Xmlrpc滿足。我需要一種方法來限制一個HList的所有元素在上下文中都有一個隱含的數據類型

由於我是一名無形的初學者,我想知道獲得此功能的最佳方式是什麼。我有一些見解,但我絕對不知道如何使用無形來完成它。理想情況是有辦法從案例類的給定屬性獲取類型,並將其明確地從0xmlrpc轉換爲,並將其類型傳遞給0xmlrpc。我想這不是如何做到的。

回答

15

首先,您需要編寫用於HList的通用序列化器。也就是說,你需要指定如何序列H :: THNil

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

然後你就可以定義爲任何類型的可以通過Generic被解構的通用串行:

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

注意,我不得不使用Lazy,否則如果您有嵌套的案例類,此代碼將破壞隱式解析過程。如果出現「發散的隱式擴展」錯誤,則可以嘗試在hconsDatatypehnilDatatype中添加Lazy以隱式參數。

這是可行的,因爲Generic.Aux[T, R]鏈接任意產品類型THList類型R。例如,對於這種情況下類

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

不成形將生成類型的Generic實例

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

因此,可以委派序列化到遞歸定義Datatype S代表HList,與該數據轉換爲HList首先是Generic。反序列化的工作原理類似,但反過來 - 首先將序列化形式讀取到HList,然後將HList轉換爲Generic的實際數據。

我可能在上面的API的使用上犯了幾個錯誤,但我想它表達了一般的想法。

如果你想使用LabelledGeneric,代碼會變得稍微複雜一些,如果你想處理用Coproducts表示的密封特徵層次結構,代碼會變得更加複雜。

我正在使用無形在我的庫中提供通用序列化機制,picopickle。我不知道有任何其他圖書館這樣做沒有形狀。您可以嘗試找到一些示例,說明如何在該庫中使用無形的代碼,但代碼有些複雜。無形例子中也有一個例子,即S-expressions

11

弗拉基米爾的回答非常好,應該是公認的答案,但也可以用Shapeless's TypeClass machinery更好地做到這一點。鑑於以下設置:

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] 

我們可以這樣寫:

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

一定要定義這些都在一起,這樣他們就會正確地陪伴。

那麼,如果我們有一個案例類:

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

和實例的各類案件類成員(在這種情況下只是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 
    } 
} 

我們自動獲得一個派生的實例for 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)) 

This works about the sa我作爲弗拉基米爾的解決方案,但它可以讓Shapeless抽象一些枯燥的類型實例派生的樣板,所以你不必用Generic弄髒你的手。