2015-04-20 17 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弄脏你的手。