[[libs:io]]
 

Pickler combinators in Scala

This demonstrates the usage of Scala’s pickler combinator library contained in scala.io.BytePickle. The library is developed and maintained by Philipp Haller. It is based on a Haskell library by Andrew Kennedy.

The first example demonstrates how to define a pickler for an abstract class with a number of case classes:

abstract class Term                              // term of lambda calculus
case class Var(s: String) extends Term           // variable  
case class Lam(s: String, t: Term) extends Term  // lambda abstraction
case class App(t1: Term, t2: Term) extends Term  // function application

Our goal is to define a pickler for values of type Term. For this, we first define picklers for the alternative cases Var, Lam and App, respectively:

def varPickler: SPU[Term] =
  wrap(Var,
       (t: Term) => t match { case Var(x) => x },
       string)
 
def lamPickler: SPU[Term] =
  wrap((p: Pair[String, Term]) => Lam(p._1, p._2),
       (t: Term) => t match { case Lam(s, t) => Pair(s, t) },
       pair(string, termPickler))
 
def appPickler: SPU[Term] =
  wrap((p: Pair[Term, Term]) => App(p._1, p._2),
       (t: Term) => t match { case App(t1, t2) => Pair(t1, t2) },
       pair(termPickler, termPickler))

Note that all picklers are defined in terms of the wrap combinator. The wrap combinator is used to pre- and post-process values before/after they are pickled/unpickled using the pickler provided in the 3rd argument. Let’s say we want to pickle the value Var(”x”). The wrap combinator will first pre-process this value (of type Term) into a string (”x”). Then it applies the string pickler to do the actual pickling. Unpickling proceeds by first unpickling a string (”x”), and then applying the post-processing function Var (the constructor of case class Var) which re-constructs the original value Var(”x”). The other picklers work in the same way. The only difference is that they use the pair combinator to construct picklers which can pickle pairs of values.

A pickler for the (single-recursive) datatype Term can be defined using the data combinator:

def termPickler: SPU[Term] =
  data(
    (t: Term) => t match {
      case Var(_)   => 0
      case Lam(_,_) => 1
      case App(_,_) => 2
    },
    List(() => varPickler, () => lamPickler, () => appPickler))

The data combinator expects a tagging function as its first argument. It is used to distinguish the different case classes defined for the abstract class Term. The tagging function maps Terms to integers which are used to index into a list of “lazy picklers” provided as the second argument to data (it is essential that these picklers are prevented from being constructed as this would lead to infinite recursion). Lazyness is achieved by providing functions that take empty parameter lists and return the respective picklers. Only when termPickler is applied to an actual value the component picklers are instantiated thereby ensuring the recursive construction terminates.

Let’s pickle something using our powerful termPickler! What about this term:

val x = Var("x")
val i = Lam("x", x)
val k = Lam("x", Lam("y", x))
val kki = App(k, App(k, i))

Pickling a value is as easy as applying the pickle function (imported from scala.io.BytePickle):

val res = pickle(termPickler, kki)

res now containes a byte array with the pickled data. Unpickling applied to this byte array should result in the original value:

val kki2 = unpickle(termPickler, res)
 
libs/io.txt · Last modified: 2010/02/11 09:10
 
Recent changes RSS feed Valid XHTML 1.0 Driven by DokuWiki