flat-0.6: Principled and efficient bit-oriented binary serialization.
Safe HaskellSafe-Inferred
LanguageHaskell2010

Flat.Tutorial

Synopsis

    Documentation

    To (de)serialise a data type, make it an instance of the Flat class.

    There is Generics based support to automatically derive a correct instance.

    Let’s see some code.

    We need a couple of extensions:

    >>> :set -XDeriveGeneric -XDeriveAnyClass
    

    The Flat top module:

    >>> import Flat
    

    And, just for fun, a couple of functions to display an encoded value as a sequence of bits:

    >>> import Flat.Instances.Test (flatBits,allBits)
    

    Define a few custom data types, deriving Generic and Flat:

    >>> data Result = Bad | Good deriving (Show,Generic,Flat)
    
    >>> data Direction = North | South | Center | East | West deriving (Show,Generic,Flat)
    
    >>> data List a = Nil | Cons a (List a) deriving (Show,Generic,Flat)
    

    Now we can encode a List of Directions using flat:

    >>> flat $ Cons North (Cons South Nil)
    "\149"
    

    The result is a strict ByteString.

    And decode it back using unflat:

    >>> unflat . flat $ Cons North (Cons South Nil) :: Decoded (List Direction)
    Right (Cons North (Cons South Nil))
    

    The result is a Decoded value: Either a DecodeException or the actual value.

    Optimal Bit-Encoding

    A pecularity of Flat is that it uses an optimal bit-encoding rather than the usual byte-oriented one.

    One bit is sufficient to encode a Result or an empty List:

    >>> flatBits Good
    "1"
    
    >>> flatBits (Nil::List Direction)
    "0"
    

    Two or three bits suffice for a Direction:

    >>> flatBits South
    "01"
    
    >>> flatBits West
    "111"
    

    For the serialisation to work with byte-oriented devices or storage, we need to add some padding.

    To do so, rather than encoding a plain value, flat encodes a PostAligned value, that's to say a value followed by a Filler that stretches till the next byte boundary.

    In practice, the padding is a, possibly empty, sequence of 0s followed by a 1.

    For example, this list encodes as 7 bits:

    >>> flatBits $ Cons North (Cons South Nil)
    "1001010"
    

    And, with the added padding of a final "1", will snugly fit in a single byte:

    >>> allBits $ Cons North (Cons South Nil)
    "10010101"
    

    But .. you don't need to worry about these details as byte-padding is automatically added by the function flat and removed by unflat.

    Pre-defined Instances

    Flat instances are already defined for relevant types of some common packages: array, base, bytestring, containers, dlist, mono-traversable, text, unordered-containers, vector.

    They are automatically imported by the Flat module.

    For example:

    >>> flatBits $ Just True
    "11"
    

    Wrapper Types

    There are a few wrapper types that modify the way encoding and/or decoding occur.