binary-typed-1.0: Type-safe binary serialization

Safe HaskellSafe




This meta-module exists only for documentational purposes; the library functionality is found in Data.Binary.Typed.



Standard Binary serializes to ByteString, which is an untyped format; deserialization of unexpected input usually results in unusable data.

This module defines a Typed type, which allows serializing both a value and the type of that value; deserialization can then check whether the received data was sent assuming the right type, and error messages may provide insight into the type mismatch.

For example, this uses Binary directly:

test1 = let val = 10 :: Int
            enc = encode val
            dec = decode enc :: Bool
        in  print dec

This behaves unexpectedly: An Int value is converted to a Bool, which corresponds to a wacky type coercion. The receiving end has no way of knowing what the incoming data should have been interpreted as.

Using Typed, this can be avoided:

test2 = let val = 10 :: Int
            enc = encode (typed Full val)
            dec = decode enc :: Typed Bool
        in  print dec

This time decode raises an error: the incoming data is tagged as an Int, but is attempted to be decoded as Bool.

Basic usage

This package is typically used for debugging purposes. Hashed32 type information keeps the size overhead relatively low, but requires a certain amount of computational ressources. It is reliable at detecting errors, but not very good at telling specifics about it. If a problem is identified, the typing level can be increased to Shown or Full, providing information about the involved types. If performance is critical, Untyped "typed" encoding can be used, with minimal overhead compared to using Binary directly.

This module exports a couple of convenience functions that have the type-mangling baked in already. The above example could have been written as

test3 = let val = 10 :: Int
            enc = encodeTyped Hashed32 val
            dec = decodeTyped enc :: Either String Bool
        in  print dec

Using encodeTyped in particular has a significant advantage: when used to create new specialized encoding functions, the type information has to be calculated only once, and can be shared among further invocations of the function. In other words, using

encodeInt :: Int -> ByteString
encodeInt = encodeTyped Hashed32

is much more efficient than

encodeInt :: Int -> ByteString
encodeInt = encode . typed Hashed32

since the latter recalculates the hash of "Int" on every invocation.

API overview

The core definitions in Data.Binary.Typed are:

In addition to those, a couple of useful helper functions with more efficient implementation than what the core definitions could offer:

  • mapTyped (change values contained in Typeds)
  • reValue (change value, but don't recompute type representation)
  • reType (change type representation, but keep value)
  • preserialize (compute serialized type representation and cache it, useful as an optimization)

Lastly, there are a number of encoding/decoding functions:

(The Data.Binary.Typed.Debug module shares the same API.)