search term:

Scala Pickling 0.10.0

pickling 0.10.0 として implicit.ly に投稿したものを訳しました。 最近コミッター権をもらいましたが、Pickling の 90% 以上は Eugene Burmako、Heather Miller、Philipp Haller によって書かれています。

Scala Pickling は、Scala のための自動シリアライゼーション・フレームワークで、0.10.0 が初の安定版となる。Pickling は高速で、ボイラープレート (冗長なお決まりコード) 無しで書くことができ、ユーザ側で (バイナリや JSON などの) シリアライゼーション・フォーマットを簡単に差し替えることができる。また、0.10.x シリーズ中はバイナリ互換性とフォーマットの互換性の両方を保つ予定だ。

Pickling を短くまとめると

ある任意の値、例えば Person("foo", 20) を pickle する (シリアライズ化を保存食に喩えて、漬物に「漬ける」と言う) とき、以下の 2つのものが必要になる:

  1. 与えられた型 Personpickler コンビネータ
  2. pickle フォーマット

Pickler[A]Aエントリーフィールドコレクションといった抽象的なものに分解することを担当する。プリミティブな pickler を合成して複合的な pickler を作ることができるため、コンビネータと呼ばれている。一方 PickleFormatフィールドなどの抽象的な概念をバイナリやテキストといった形に具現化する。

Defaults モード

以下は基本形である Defaults モードの使用例だ。

scala> import scala.pickling.Defaults._, scala.pickling.json._
scala> case class Person(name: String, age: Int)

scala> val pkl = Person("foo", 20).pickle
pkl: pickling.json.pickleFormat.PickleType =
JSONPickle({
  "$type": "Person",
  "name": "foo",
  "age": 20
})

scala> val person = pkl.unpickle[Person]
person: Person = Person(foo,20)

この Defaults モードは、プリミティブ pickler 群から Pickler[Person] をコンパイル時に自動的に導出する! コードが静的に生成されるため、文字列の操作などもインライン化して高速化している。(同じくスキーマを使わない Java serialization や Kryo に比べても速い)

ここで注目してほしいのは、Pickler[A] は型クラスであるため、Person クラスを改変して Serializableのようなものを継承するといったことをせずに追加導入することができることだ。

カスタム・プロトコル・スタック

Pickling 0.10.0 からの新機能として pickler、演算子、フォーマットが別々の trait として提供されるようになったため、サードパーティーのライブラリ側が好みで積み上げてカスタム・モードを提供することができるようになった。例えば、プリミティブ型と Apple だけを pickle したくて、自動的な pickler コンビネータの導出はして欲しくないとする。以下が、カスタム・モードになる:

scala> case class Apple(kind: String)
scala> val appleProtocol = {
         import scala.pickling._
         new pickler.PrimitivePicklers with pickler.RefPicklers
             with json.JsonFormats {
           // Manually generate pickler for Apple
           implicit val applePickler = PicklerUnpickler.generate[Apple]
           // Don't fall back to runtime picklers
           implicit val so = static.StaticOnly
           // Provide custom functions
           def toJsonString[A: Pickler](a: A): String =
             functions.pickle(a).value
           def fromJsonString[A: Unpickler](s: String): A =
             functions.unpickle[A](json.JSONPickle(s))
         }
       }

ユーザ側はこのように使う:

scala> import appleProtocol._

scala> toJsonString(Apple("honeycrisp"))
res0: String =
{
  "$type": "Apple",
  "kind": "honeycrisp"
}

より詳しくは以下の資料を参照