Say I have two traits that I would like to mixin to a class. The traits each implement an abstract method that the class needs.
trait Writable {
def serialize(out: java.io.DataOutput)
}
trait T1 extends Writable
trait A extends T1 {
val aNum: Int
abstract override def serialize(out: java.io.DataOutput) = {
super.serialize(out)
println("A serialize")
out.writeInt(aNum)
}
def action = println("A action")
}
trait B extends T1 {
val bNum: Int
abstract override def serialize(out: java.io.DataOutput) = {
super.serialize(out)
println("B serialize")
out.writeInt(bNum)
}
def action = println("B action")
}
abstract class M[CT1 <: T1](val mNum: Int) extends Writable {
this: M[CT1] with T1 =>
def serialize(out: java.io.DataOutput) = {
println("M serialize")
out.writeInt(mNum)
}
def action
}
I can then construct a concrete M with either A or B and serialize:
scala> val m1 = new M[A](10) with A { val aNum = 20 }
m1: M[A] with A = $anon$1@67c1e630
scala> val m2 = new M[B](20) with B { val bNum = 30 }
m2: M[B] with B = $anon$1@c36f58e
scala> val out = new java.io.DataOutputStream(new java.io.ByteArrayOutputStream())
out: java.io.DataOutputStream = java.io.DataOutputStream@87afebf
scala> m1.serialize(out)
M serialize
A serialize
scala> m2.serialize(out)
M serialize
B serialize
Everything works as expected. But how do I deserialize the objects while respecting the type of trait that is mixed into M? I could output the trait's name in the serialize method and then have M's deserialize method dispatch on the name but what if I have classes other than M that A and B can be mixed into? Then, each class would have to duplicate the behavior of M's dispatching deserialize. The problem gets even worse if I have multiple traits that need to get mixed into an object to make it concrete and each has it's own custom serialization/deserialization to do. Anyone tackle an issue like this?
Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory.
Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.
Serialization and deserialization work together to transform/recreate data objects to/from a portable format. Serialization enables us to save the state of an object and recreate the object in a new location. Serialization encompasses both the storage of the object and exchange of data.
To wit, serialization is the process of converting a data object into a byte stream, and saving the state of the object to be stored on a disk or transmitted across a network. This cuts down the storage size needed and makes it easier to transfer information over a network. Serialization Process.
Yes, people have. The way to go is to use typeclass pattern championed by David MacIver's sbinary and Debasish Ghosh's sjson. Debasish's trilogy
are especially useful to all intermediate Scala programmers.
Today, many of the libraries are adopting this methodology including mine scalaxb. See
I've borrowed the naming idea from Scala Collections' CanBuildFrom
and named my typeclasses as follows:
trait CanReadXML[A] {
def reads(seq: scala.xml.NodeSeq): Either[String, A]
}
trait CanWriteXML[A] {
def writes(obj: A, namespace: Option[String], elementLabel: Option[String],
scope: NamespaceBinding, typeAttribute: Boolean): NodeSeq
}
trait XMLFormat[A] extends CanWriteXML[A] with CanReadXML[A]
Edit:
Could you explain to me how the framework chooses between "with A" or "with B"?
Using the typeclass pattern, the libraries mixes in neither A
nor B
.
To take scalaxb for example, it provides a method called scalaxb.fromXML
in the package object defined as follows:
def fromXML[A](seq: NodeSeq, stack: List[ElemName] = Nil)
(implicit format: XMLFormat[A]): A = format.reads(seq, stack) match {
case Right(a) => a
case Left(a) => throw new ParserFailure(a)
}
Given that you have XML document, and you want to unmarshal (deserialize) it to ipo.Address
object, you would call
scalaxb.fromXML[ipo.Address](<shipTo xmlns="http://www.example.com/IPO">
<name>Foo</name>
<street>1537 Paper Street</street>
<city>Wilmington</city>
</shipTo>)
The Address
object stays pure using the typeclass pattern:
case class Address(name: String, street: String, city: String)
How does the compiler know what to do? The magic is the implicit parameter required by fromXML
called implicit format: XMLFormat[A]
. This requires that you have XMLFormat[Address]
available as an implicit value within the scope where scalaxb.fromXML[ipo.Address](...)
is called.
This made available in the code generated by scalaxb because it mixes in XMLProtocol
into the package object of ipo
package. And ipo.XMLProtocol
defines
implicit lazy val IpoAddressFormat: scalaxb.XMLFormat[ipo.Address] = new DefaultIpoAddressFormat {}
Edit2:
I think I am starting to understand the actual question. You have an object consisting of trait mixins, and you want to somehow "deserialize" the trait composition on other process. As you wrote, you could include some tag for each traits, and load whatever you can.
Since I've written so far on typeclass pattern, let me continue with the approach. The neat thing about having serialization code outside of the object is that you could actually describe mixin combination of the object. Suppose there are traits
trait Foo { def foo: Int }
trait Bar { def bar: Int }
and you want to describe the mixin as <obj><foo>1</foo><bar>2</bar></obj>
.
Here's a gist I whipped up.
I defined typeclass instance for Foo
, Bar
, and Foo with Bar
, and called
Def.fromXML[Foo with Bar](<obj><foo>1</foo><bar>2</bar></obj>)
which returned
Right(FooWithBar(1, 2))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With