Bytes.Decode

Decoders

type Decoder

Describes how to turn a sequence of bytes into a nice Gren value.

decode : Decoder a -> Bytes -> Maybe a

Turn a sequence of bytes into a nice Gren value.

-- decode (unsignedInt16 BE) <0007> == Just 7
-- decode (unsignedInt16 LE) <0700> == Just 7
-- decode (unsignedInt16 BE) <0700> == Just 1792
-- decode (unsignedInt32 BE) <0700> == Nothing

The Decoder specifies exactly how this should happen. This process may fail if the sequence of bytes is corrupted or unexpected somehow. The examples above show a case where there are not enough bytes.

Integers

signedInt8 : Decoder Int

Decode one byte into an integer from -128 to 127.

signedInt16 : Endianness -> Decoder Int

Decode two bytes into an integer from -32768 to 32767.

signedInt32 : Endianness -> Decoder Int

Decode four bytes into an integer from -2147483648 to 2147483647.

unsignedInt8 : Decoder Int

Decode one byte into an integer from 0 to 255.

unsignedInt16 : Endianness -> Decoder Int

Decode two bytes into an integer from 0 to 65535.

unsignedInt32 : Endianness -> Decoder Int

Decode four bytes into an integer from 0 to 4294967295.

Floats

float32 : Endianness -> Decoder Float

Decode four bytes into a floating point number.

float64 : Endianness -> Decoder Float

Decode eight bytes into a floating point number.

Bytes

bytes : Int -> Decoder Bytes

Copy a given number of bytes into a new Bytes sequence.

Strings

string : Int -> Decoder String

Decode a given number of UTF-8 bytes into a String.

Most protocols store the width of the string right before the content, so you will probably write things like this:

import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode

sizedString : Decode.Decoder String
sizedString =
  Decode.unsignedInt32 BE
    |> Decode.andThen Decode.string

In this case we read the width as a 32-bit unsigned integer, but you have the leeway to read the width as a Base 128 Varint for ProtoBuf, a Variable-Length Integer for SQLite, or whatever else they dream up.

Map

map : (a -> b) -> Decoder a -> Decoder b

Transform the value produced by a decoder. If you encode negative numbers in a special way, you can say something like this:

negativeInt8 : Decoder Int
negativeInt8 =
  map negate unsignedInt8

In practice you may see something like ProtoBuf’s ZigZag encoding which decreases the size of small negative numbers.

map2 : (a -> b -> result) -> Decoder a -> Decoder b -> Decoder result

Combine two decoders.

import Bytes exposing ( Endianness(..) )
import Bytes.Decode as Decode

type alias Point =
    { x : Float
    , y : Float
    }

makePoint : Float -> Float -> Point
makePoint x y =
    { x = x
    , y = y
    }

decoder : Decode.Decoder Point
decoder =
    Decode.map2 makePoint (Decode.float32 BE) (Decode.float32 BE)
map3 : (a -> b -> c -> result) -> Decoder a -> Decoder b -> Decoder c -> Decoder result

Combine three decoders.

map4 : (a -> b -> c -> d -> result) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder result

Combine four decoders.

map5 : (a -> b -> c -> d -> e -> result) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder result

Combine five decoders. If you need to combine more things, it is possible to define more of these with map2 or andThen.

And Then

andThen : (a -> Decoder b) -> Decoder a -> Decoder b

Decode something and then use that information to decode something else. This is most common with strings or sequences where you need to read how long the value is going to be:

import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode

string : Decoder String
string =
  Decode.unsignedInt32 BE
    |> Decode.andThen Decode.string

Check out the docs for succeed, fail, and loop to see andThen used in more ways!

succeed : a -> Decoder a

A decoder that always succeeds with a certain value. Maybe we are making a Maybe decoder:

import Bytes.Decode as Decode exposing (Decoder)

maybe : Decoder a -> Decoder (Maybe a)
maybe decoder =
  let
    helper n =
      if n == 0 then
        Decode.succeed Nothing
      else
        Decode.map Just decoder
  in
  Decode.unsignedInt8
    |> Decode.andThen helper

If the first byte is 00000000 then it is Nothing, otherwise we start decoding the value and put it in a Just.

fail : Decoder a

A decoder that always fails. This can be useful when using andThen to decode custom types:

import Bytes exposing (Endianness(..))
import Bytes.Encode as Encode
import Bytes.Decode as Decode

type Distance = Yards Float | Meters Float

toEncoder : Distance -> Encode.Encoder
toEncoder distance =
  case distance of
    Yards n -> Encode.sequence [ Encode.unsignedInt8 0, Encode.float32 BE n ]
    Meters n -> Encode.sequence [ Encode.unsignedInt8 1, Encode.float32 BE n ]

decoder : Decode.Decoder Distance
decoder =
  Decode.unsignedInt8
    |> Decode.andThen pickDecoder

pickDecoder : Int -> Decode.Decoder Distance
pickDecoder tag =
  case tag of
    0 -> Decode.map Yards (Decode.float32 BE)
    1 -> Decode.map Meters (Decode.float32 BE)
    _ -> Decode.fail

The encoding chosen here uses an 8-bit unsigned integer to indicate which variant we are working with. If we are working with yards do this, if we are working with meters do that, and otherwise something went wrong!

Loop

type Step
= Loop state
| Done a

Decide what steps to take next in your loop.

If you are Done, you give the result of the whole loop. If you decide to Loop around again, you give a new state to work from. Maybe you need to add an item to a list? Or maybe you need to track some information about what you just saw?

Note: It may be helpful to learn about finite-state machines to get a broader intuition about using state. I.e. You may want to create a type that describes four possible states, and then use Loop to transition between them as you consume characters.

loop : state -> (state -> Decoder (Step state a)) -> Decoder a

A decoder that can loop indefinitely. This can be helpful when parsing repeated structures, like a list:

import Bytes exposing (Endianness(..))
import Bytes.Decode as Decode exposing (..)

list : Decoder a -> Decoder (Array a)
list decoder =
  unsignedInt32 BE
    |> andThen (\len -> loop (len, []) (listStep decoder))

listStep : Decoder a -> { fst : Int, snd : Array a } -> Decoder (Step { fst Int, snd : Array a } (Array a))
listStep decoder (n, xs) =
  if n <= 0 then
    succeed (Done xs)
  else
    map (\x -> Loop (n - 1, x :: xs)) decoder

The list decoder first reads a 32-bit unsigned integer. That determines how many items will be decoded. From there we use loop to track all the items we have parsed so far and figure out when to stop.