我目前正在实现一个库,用于对XML-RPC消息进行序列化和反序列化。它几乎已经完成,但现在我正在尝试使用无形删除当前 asProduct 方法的样板。我目前的代码:
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}
}
我的目标是允许我的库的用户序列化/反序列化case类,而不强迫他编写样板代码。目前,您必须使用前面提到的asProduct方法声明case类和隐式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)
这是使用类型序列化和反序列化的经典策略。
目前,我已经掌握了如何通过 Generic 或 LabelledGeneric 将案例类转换为HList。问题是,一旦我完成了这个转换,我就可以像在asProduct2示例中那样调用方法 fromXmlrpc 和 toXmlrpc 。我没有关于case类中属性类型的任何信息,因此,编译器找不到任何满足 fromXmlrpc 和 toXmlrpc 的隐式。我需要一种方法来约束HList的所有元素在上下文中都有一个隐式数据类型。
由于我是Shapeless的初学者,我想知道获得此功能的最佳方式是什么。我有一些见解,但我绝对不知道如何使用Shapeless完成它。理想的方法是从case类的给定属性中获取类型,并将此类型显式传递给 fromXmlrpc 和 toXmlrpc 。我想这不是怎么做的。
答案 0 :(得分:15)
首先,您需要为HList
编写通用序列化程序。也就是说,您需要指定序列化H :: T
和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)
}
然后,您可以为可以通过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
,否则如果您有嵌套的案例类,则此代码会破坏隐式解析过程。如果您遇到“分歧隐式扩展”错误,您可以尝试将Lazy
添加到hconsDatatype
和hnilDatatype
中的隐式参数中。
这是有效的,因为Generic.Aux[T, R]
链接任意类似产品的类型T
和HList
类型R
。例如,对于此案例类
case class A(x: Int, y: String)
无形将生成类型
的Generic
实例
Generic.Aux[A, Int :: String :: HNil]
因此,您可以将序列化委派给Datatype
的递归定义的HList
,并首先将数据转换为HList
Generic
。反序列化的工作方式类似,但相反 - 首先将序列化表单读取到HList
,然后将HList
转换为Generic
的实际数据。
我可能在上面NodeSeq
API的使用中犯了几个错误,但我想它传达了一般的想法。
如果您想使用LabelledGeneric
,代码会变得稍微复杂一些,如果您想处理用Coproduct
s表示的密封特征层次结构,则会更加复杂。
我正在使用无形在我的库picopickle中提供通用序列化机制。我不知道有任何其他图书馆用无形的方式做到这一点。您可以尝试找到一些示例,如何在此库中使用无形状,但代码有些复杂。在无形的例子中也有一个例子,即S-expressions。
答案 1 :(得分:12)
弗拉基米尔的答案很棒,应该是被接受的答案,但也可以用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
}
}
我们自动获取Foo
的派生实例:
scala> case class Foo(bar: String, baz: String)
defined class Foo
scala> val fooDatatype = implicitly[Datatype[Foo]]
fooDatatype: Datatype[Foo] = Datatype$typeClass$$anon$3@2e84026b
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))
这与Vladimir的解决方案大致相同,但是它让Shapeless抽象出类型实例派生类型的无聊样板,因此您不必为Generic
弄脏手。