2015-04-20 12 views
11

Tôi hiện đang triển khai một thư viện để tuần tự hóa và deserialize đến và từ các tin nhắn XML-RPC. Nó gần như đã hoàn tất nhưng bây giờ tôi đang cố gắng loại bỏ bản mẫu của phương pháp asProduct hiện tại của tôi bằng cách sử dụng Shapeless. Mã hiện tại của tôi:Làm thế nào để các lớp vỏ trường hợp không ổn định với các thuộc tính và typeclasses?

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

Mục tiêu của tôi là cho phép người dùng thư viện sắp xếp/sắp xếp lại các trường hợp không có mã. Hiện tại, bạn phải khai báo lớp case và một val ẩn bằng cách sử dụng phương thức asProduct đã nói ở trên để có một cá thể Datatype trong ngữ cảnh. ngầm này được sử dụng trong đoạn mã sau:

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) 

Đây là chiến lược kinh điển của serializing và deserializing sử dụng các lớp học kiểu.

Tại thời điểm này, tôi đã nắm được thế nào để chuyển đổi từ trường hợp lớp học để HList qua Generic hoặc LabelledGeneric. Vấn đề là khi tôi thực hiện chuyển đổi này, tôi có thể gọi phương thức từXmlrpctoXmlrpc như trong ví dụ asProduct2. Tôi không có bất kỳ thông tin nào về các loại thuộc tính trong lớp trường hợp và do đó, trình biên dịch không thể tìm thấy bất kỳ ẩn nào đáp ứng từXmlrpctoXmlrpc. Tôi cần một cách để hạn chế rằng tất cả các yếu tố của một HList có một kiểu dữ liệu tiềm ẩn trong ngữ cảnh.

Vì tôi là người mới bắt đầu với Shapeless, tôi muốn biết cách tốt nhất để nhận chức năng này là gì. Tôi có một số hiểu biết nhưng tôi chắc chắn không có ý tưởng làm thế nào để làm cho nó được thực hiện bằng cách sử dụng Shapeless. Lý tưởng sẽ có một cách để có được các loại từ một thuộc tính nhất định của lớp trường hợp và thông qua loại hình này một cách rõ ràng để fromXmlrpctoXmlrpc. Tôi tưởng tượng rằng đây không phải là cách nó có thể được thực hiện.

Trả lời

15

Trước tiên, bạn cần viết bộ nối tiếp chung cho HList. Nghĩa là, bạn cần phải xác định làm thế nào để serialize 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) 
    } 

Sau đó, bạn có thể định nghĩa một serializer chung cho bất kỳ loại có thể được deconstructed qua 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) 
    } 

Lưu ý rằng tôi phải sử dụng Lazy bởi vì nếu không mã này sẽ phá vỡ quá trình giải quyết ngầm nếu bạn có các lớp vỏ lồng nhau. Nếu bạn gặp lỗi "mở rộng gián đoạn phân tách", bạn có thể thử thêm Lazy vào các tham số ngầm trong hconsDatatypehnilDatatype.

Điều này hoạt động vì Generic.Aux[T, R] liên kết loại sản phẩm tùy ý giống như THList loại R.Ví dụ, đối với lớp trường hợp này

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

hình thù sẽ tạo ra một trường hợp Generic loại

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

Do đó, bạn có thể ủy serialization đệ quy được xác định Datatype s cho HList, chuyển đổi dữ liệu để HList với Generic trước tiên. Deserialization hoạt động tương tự nhưng ngược lại - đầu tiên dạng tuần tự được đọc đến HList và sau đó HList này được chuyển đổi thành dữ liệu thực tế với Generic.

Có thể tôi đã phạm một số sai lầm trong việc sử dụng API NodeSeq ở trên nhưng tôi đoán nó truyền tải ý tưởng chung.

Nếu bạn muốn sử dụng LabelledGeneric, mã sẽ trở nên phức tạp hơn một chút, và thậm chí nhiều hơn thế nếu bạn muốn xử lý phân cấp đặc điểm được niêm phong được biểu thị bằng Coproduct s.

Tôi đang sử dụng không có khả năng cung cấp cơ chế tuần tự hóa chung trong thư viện của mình, picopickle. Tôi không biết về bất kỳ thư viện nào khác thực hiện điều này với bất ổn. Bạn có thể thử và tìm một số ví dụ làm thế nào shapeless có thể được sử dụng trong thư viện này, nhưng mã có phần nào phức tạp. Ngoài ra còn có một ví dụ trong số các ví dụ không may, cụ thể là S-expressions.

11

Câu trả lời của Vladimir rất tuyệt và phải là câu trả lời được chấp nhận, nhưng cũng có thể thực hiện điều này một cách độc đáo hơn một chút với Shapeless's TypeClass machinery. Với thiết lập như sau:

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] 

Chúng tôi có thể viết này:

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

Hãy chắc chắn để xác định các tất cả cùng nhau vì vậy họ sẽ được companioned đúng cách.

Sau đó, nếu chúng ta có một lớp trường hợp:

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

Và trường hợp cho các loại của các thành viên trường hợp lớp (trong trường hợp này chỉ 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 
    } 
} 

Chúng tôi tự động nhận được một trường hợp có nguồn gốc cho 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)) 

Điều này hoạt động về sa tôi như là giải pháp của Vladimir, nhưng nó cho phép Shapeless tóm tắt một số bản mẫu nhàm chán của bản thể loại lớp dẫn xuất, do đó bạn không cần phải có được bàn tay của bạn bẩn với Generic.

Các vấn đề liên quan