Richard Searle


The crippled codec

06 Sep 2014

The scodec library implements a combinator based encoding and decoding library for Scala.

My use case only required encoding, but all the combinators are designed for codecs (which are bidirectional). I could write an additional set of encoder only combinators, but that seemed rather pointless. Rather I created a simple mechanism to “stub out” the unused decoder functionality.

A primary reason for ignoring the decoder path was to maximize the win over a legacy Java implementation! The LOSC reduction was little disappointing, being only 15x. That is not much better than the 10x average seen for other legacy Java re-implementations

Consider the implementation of a two digit Binary Coded Decimal number

val bcd2: Codec[Int] = (uint4 ~ uint4).xmap(
     v => v._1 * 10 + v._2,
     d => (d / 10) ~ (d % 10))

This could be reduced to

val bcd2: Codec[Int] = (uint4 ~ uint4).xmap(
     d => (d / 10) ~ (d % 10))

which will generate a NullPointerException if the decoder path is referenced. This design would probably result in howls of derision from the Scala community.

val bcd2: Codec[Int] = (uint4 ~ uint4).xmap(
     _ => ???,
     d => (d / 10) ~ (d % 10))

Would be a little more idiomatic. It will still fail if the decoder path is called but that cannot be avoided.

This design clutters the code and still leaves the decoder path visible (and subject to error). What we need is a combinator that only provides the encoding path.

It is unlikely that many users of the scodec library would find this combinator useful, so it cannot be sensibly added to the existing combinators. The pimp-my-library pattern via an implicit class would be more appropriate.

private implicit class CrippledCodec[A](c: Codec[A]) {
    def apply[B](g: (B) => A): Codec[B] = c.xmap(_ => ???, g)

With the final code being

val bcd2: Codec[Int] = (uint4 ~ uint4)(d => (d / 10) ~ (d % 10))

Note that this formulation does not compile, complaining that toInt is not a member of B

private implicit class CrippledCodec[A,B](c: Codec[A]) {
    def apply(g: (B) => A): Codec[B] = c.xmap(_ => ???, g)