cardano-ledger-binary-1.5.0.0: Binary serialization library used throughout ledger
Safe HaskellSafe-Inferred
LanguageHaskell2010

Cardano.Ledger.Binary.Coders

Description

Cardano.Ledger.Binary.Coders provides tools for writing EncCBOR and DecCBOR instances (see module Binary) in an intuitive way that mirrors the way one constructs values of a particular type. Advantages include:

  1. Book-keeping details neccesary to write correct instances are hidden from the user.
  2. Inverse EncCBOR and DecCBOR instances have visually similar definitions.
  3. Advanced instances involving sparse-encoding, compact-representation, and Annotator instances are also supported.

A Guide to Visual inspection of Duality in Encode and Decode

  1. (Sum c) and (SumD c) are duals
  2. (Rec c) and (RecD c) are duals
  3. (Keyed c) and (KeyedD c) are duals
  4. (OmitC x) and (Emit x) are duals
  5. (Omit p ..) and (Emit x) are duals if (p x) is True
  6. (To x) and (From) are duals if (x::T) and (forall (y::T). isRight (roundTrip y))
  7. (E enc x) and (D dec) are duals if (forall x . isRight (roundTrip' enc dec x))
  8. (f !> x) and (g <! y) are duals if (f and g are duals) and (x and y are duals)

Duality properties of (Summands name decodeT) and (SparseKeyed name (init::T) pick required) also exist but are harder to describe succinctly.

Synopsis

Creating encoders.

data Encode (w ∷ Wrapped) t where Source #

A first-order domain specific langage for describing EncCBOR instances. Applying the interpreter encode to a well-typed (Encode w T) always produces a valid encoding for T. Constructing an Encode of type T is just like building a value of type T, applying a constructor of T to the correctly typed arguments. For example

data T = T Bool Word

instance EncCBOR T where
  encCBOR (T b w) = encode (Rec T !> To b !> To w)

Note the similarity of

(T b w) and (T $ b $ w) and (Rec T !> To b !> To w)

Where (!>) is the infx version of ApplyE with the same infixity and precedence as ($). Note how the constructor and each (component, field, argument) is labeled with one of the constructors of Encode, and are combined with the application operator (!>). Using different constructors supports different styles of encoding.

Constructors

Rec ∷ t → Encode ('Closed 'Dense) t

Label the constructor of a Record-like datatype (one with exactly 1 constructor) as an Encode.

Sum ∷ t → WordEncode 'Open t

Label one of the constructors of a sum datatype (one with multiple constructors) as an Encode

Keyed ∷ t → Encode ('Closed 'Sparse) t

Label the constructor of a Record-like datatype as being encoded sparsely (storing only non-default values).

ToEncCBOR a ⇒ a → Encode ('Closed 'Dense) a

Label an (component, field, argument) to be encoded using an existing EncCBOR instance.

ToGroupEncCBORGroup t ⇒ t → Encode ('Closed 'Dense) t

Label components, set of fields, or arguments to be encoded using an existing EncCBORGroup instance.

E ∷ (t → Encoding) → t → Encode ('Closed 'Dense) t

Label an (component, field, argument) to be encoded using an existing EncCBOR instance.

MapE ∷ (a → b) → Encode w a → Encode w b

Lift one Encode to another with a different type. Used to make a Functor instance of (Encode w).

OmitC ∷ t → Encode w t

Skip over the (component,field, argument), don't encode it at all (used in sparse encoding).

TagWordEncode ('Closed x) t → Encode ('Closed x) t

Precede the given encoding (in the produced bytes) with the given tag Word.

Omit ∷ (t → Bool) → Encode ('Closed 'Sparse) t → Encode ('Closed 'Sparse) t

Omit the (component,field, argument) if the function is True, otherwise encode with the given encoding.

KeyWordEncode ('Closed 'Dense) t → Encode ('Closed 'Sparse) t

Precede the encoding (in the produced bytes) with the key Word. Analagous to Tag, but lifts a Dense encoding to a Sparse encoding.

ApplyEEncode w (a → t) → Encode ('Closed r) a → Encode w t

Apply a functional encoding (arising from Rec or Sum) to get (type wise) smaller encoding. A fully saturated chain of ApplyE will be a complete encoding. See also !> which is infix ApplyE.

(!>)Encode w (a → t) → Encode ('Closed r) a → Encode w t infixl 4 Source #

Infix operator version of ApplyE. Has the same infxity and operator precedence as $

encodeEncode w t → Encoding Source #

Translate a first-order @(Encode w d) domain specific langage program, into an Encoding .

Index types for well-formed Coders.

runEEncode w t → t Source #

encodeDual ∷ ∀ t. (EncCBOR t, DecCBOR t) ⇒ t → Encode ('Closed 'Dense) t Source #

Use encodeDual and decodeDual, when you want to guarantee that a type has both EncCBOR and FromCBR instances.

Containers, Combinators, Annotators

Creating decoders.

data Decode (w ∷ Wrapped) t where Source #

The type (Decode t) is designed to be dual to (Encode t). It was designed so that in many cases a decoder can be extracted from an encoder by visual inspection. We now give some example of (Decode t) and (Encode t) pairs.

An example with 1 constructor (a record) uses Rec and RecD

In this example, let Int and C have EncCBOR instances.

data C = C { unC :: Text.Text }
instance EncCBOR C where
  encCBOR (C t) = encCBOR t
instance DecCBOR C where
  decCBOR = C $ decCBOR

data B = B { unB :: Text.Text }

data A = ACon Int B C

encodeA :: A -> Encode ('Closed 'Dense) A
encodeA (ACon i b c) = Rec ACon !> To i !> E (encCBOR . unB) b !> To c

decodeA :: Decode ('Closed 'Dense) A
decodeA = RecD ACon From <! D (B <$ decCBOR) <! From

instance EncCBOR A where
  encCBOR = encode . encodeA
instance DecCBOR A where
  decCBOR = decode decodeA

An example with multiple constructors uses Sum, SumD, and Summands.

data N = N1 Int | N2 B Bool | N3 A

encodeN :: N -> Encode 'Open N
encodeN (N1 i)    = Sum N1 0 !> To i
encodeN (N2 b tf) = Sum N2 1 !> E (encCBOR . unB) b !> To tf
encodeN (N3 a)    = Sum N3 2 !> To a

decodeN :: Decode ('Closed 'Dense) N    -- Note each clause has an 'Open decoder,
decodeN = Summands N decodeNx           -- But Summands returns a ('Closed 'Dense) decoder
  where decodeNx 0 = SumD N1 <! From
        decodeNx 1 = SumD N2 D (B <$ decCBOR) <! From
        decodeNx 3 = SumD N3 <! From
        decodeNx k = Invalid k

instance EncCBOR N   where encCBOR x = encode(encodeN x)
instance DecCBOR N where decCBOR = decode decodeN

Two examples using variants of sparse encoding for records, i.e. those datatypes with only one constructor. The Virtual constructor approach using Summands, OmitC, Emit. The Sparse field approach using Keyed, Key and Omit. The approaches work because encoders and decoders don't put fields with default values in the Encoding, and reconstruct the default values on the decoding side. We will illustrate the two approaches using the datatype M

data M = M Int [Bool] Text.Text
  deriving (Show, Typeable)

a0, a1, a2, a3 :: M  -- Some illustrative examples, using things that might be given default values.
a0 = M 0 [] ABC
a1 = M 0 [True] ABC
a2 = M 9 [] ABC
a3 = M 9 [False] ABC

The virtual constructor strategy pretends there are mutiple constructors Even though there is only one. We use invariants about the data to avoid encoding some of the values. Note the use of Sum with virtual constructor tags 0,1,2,3

encM :: M -> Encode 'Open M
encM (M 0 [] t) = Sum M 0 !> OmitC 0 !> OmitC [] !> To t
encM (M 0 bs t) = Sum M 1 !> OmitC 0 !> To bs !> To t
encM (M n [] t) = Sum M 2 !> To n !> OmitC [] !> To t
encM (M n bs t) = Sum M 3 !> To n !> To bs !> To t

decM :: Word -> Decode 'Open M
decM 0 = SumD M <! Emit 0 <! Emit [] <! From  -- The virtual constructors tell which fields have been Omited
decM 1 = SumD M <! Emit 0 <! From <! From     -- So those fields are reconstructed using Emit.
decM 2 = SumD M <! From <! Emit [] <! From
decM 3 = SumD M <! From <! From <! From
decM n = Invalid n

instance EncCBOR M where
  encCBOR m = encode (encM m)

instance DecCBOR M where
  decCBOR = decode (Summands M decM)

The Sparse field approach uses N keys, one for each field that is not defaulted. For example (M 9 [True] (pack "hi"))). Here zero fields are defaulted, so there should be 3 keys. Encoding this example would look something like this.

[TkMapLen 3,TkInt 0,TkInt 9,TkInt 1,TkListBegin,TkBool True,TkBreak,TkInt 2,TkString "hi"]
                  ^key            ^key                                    ^key

So the user supplies a function, that encodes every field, each field must use a unique key, and fields with default values have Omit wrapped around the Key encoding. The user must ensure that there is NOT an Omit on a required field. encM2 is an example.

encM2:: M -> Encode ('Closed 'Sparse) M
encM2 (M n xs t) =
    Keyed M
       !> Omit (== 0) (Key 0 (To n))    -- Omit if n is zero
       !> Omit null (Key 1 (To xs))     -- Omit if xs is null
       !> Key 2 (To t)                  -- Always encode t

To write an Decoder we must pair a decoder for each field, with a function that updates only that field. We use the Field GADT to construct these pairs, and we must write a function, that for each field tag, picks out the correct pair. If the Encode and Decode don't agree on how the tags correspond to a particular field, things will fail.

boxM :: Word -> Field M
boxM 0 = field update0 From
  where
    update0 n (M _ xs t) = M n xs t
boxM 1 = field update1 From
  where
    update1 xs (M n _ t) = M n xs t
boxM 2 = field update2 From
  where
    update2 t (M n xs _) = M n xs t
boxM n = invalidField n

Finally there is a new constructor for Decode, called SparseKeyed, that decodes field keyed sparse objects. The user supplies an initial value and field function, and a list of tags of the required fields. The initial value should have default values and any well type value in required fields. If the encode function (baz above) is encoded properly the required fields in the initial value should always be over overwritten. If it is not written properly, or a bad encoding comes from somewhere else, the intial values in the required fields might survive decoding. The list of required fields is checked.

instance DecCBOR M where
  decCBOR = decode (SparseKeyed
                      TT                        -- ^ Name of the type being decoded
                      (M 0 [] (Text.pack "a"))  -- ^ The default value
                      boxM                      -- ^ The Field function
                      [(2, Stringpart)]         -- ^ The required Fields
                    )

instance EncCBOR M where
  encCBOR m = encode(encM2 m)

Constructors

RecD ∷ t → Decode ('Closed 'Dense) t

Label the constructor of a Record-like datatype (one with exactly 1 constructor) as a Decode.

SumD ∷ t → Decode 'Open t

Label the constructor of a Record-like datatype (one with multiple constructors) as an Decode.

SummandsText → (WordDecode 'Open t) → Decode ('Closed 'Dense) t

Lift a Word to Decode function into a DeCode for a type with multiple constructors.

SparseKeyed

Lift a Word to Field function into a DeCode for a type with 1 constructor stored sparsely

Fields

  • Typeable t
     
  • String

    Name of the Type (for error messages)

  • → t

    The type with default values in all fields

  • → (WordField t)

    What to do with key in the Word

  • → [(Word, String)]

    Pairs of keys and Strings which must be there (default values not allowed)

  • Decode ('Closed 'Dense) t
     
KeyedD ∷ t → Decode ('Closed 'Sparse) t

Label a (component, field, argument) as sparsely stored, which will be populated with the default value.

FromDecCBOR t ⇒ Decode w t

Label a (component, field, argument). It will be decoded using the existing DecCBOR instance at t

FromGroup ∷ (EncCBORGroup t, DecCBORGroup t) ⇒ Decode w t

Label components, fields, arguments. It will be decoded using the existing DecCBORGroup instance at t

D ∷ (∀ s. Decoder s t) → Decode ('Closed 'Dense) t

Label a (component, field, argument). It will be decoded using the given decoder.

ApplyDDecode w1 (a → t) → Decode ('Closed d) a → Decode w1 t

Apply a functional decoding (arising from RecD or SumD) to get (type wise) smaller decoding.

InvalidWordDecode w t

Mark a Word as a Decoding which is not a valid Decoding. Used when decoding sums that are tagged out of range.

Map ∷ (a → b) → Decode w a → Decode w b

Used to make (Decode w) an instance of Functor.

TagDWordDecode ('Closed x) t → Decode ('Closed x) t

Assert that the next thing decoded must be tagged with the given word.

Emit ∷ t → Decode w t

Decode the next thing, not by inspecting the bytes, but pulled out of thin air, returning t. Used in sparse decoding.

AnnDecode w t → Decode w (Annotator t)

Lift a (Decode w t) to a (Decode w (Annotator t)). Used on a (component, field, argument) that was not Annotator encoded, but contained in Record or Sum which is Annotator encoded.

ApplyAnn

Apply a functional decoding (arising from RecD or SumD that needs to be Annotator decoded) to get (type wise) smaller decoding.

Fields

ApplyErrDecode w1 (a → Either String t) → Decode ('Closed d) a → Decode w1 t

the function to Either can raise an error when applied by returning (Left errorMessage)

Instances

Instances details
Applicative (Decode ('Closed d)) Source # 
Instance details

Defined in Cardano.Ledger.Binary.Decoding.Coders

Methods

pure ∷ a → Decode ('Closed d) a Source #

(<*>)Decode ('Closed d) (a → b) → Decode ('Closed d) a → Decode ('Closed d) b Source #

liftA2 ∷ (a → b → c) → Decode ('Closed d) a → Decode ('Closed d) b → Decode ('Closed d) c Source #

(*>)Decode ('Closed d) a → Decode ('Closed d) b → Decode ('Closed d) b Source #

(<*)Decode ('Closed d) a → Decode ('Closed d) b → Decode ('Closed d) a Source #

Functor (Decode w) Source # 
Instance details

Defined in Cardano.Ledger.Binary.Decoding.Coders

Methods

fmap ∷ (a → b) → Decode w a → Decode w b Source #

(<$) ∷ a → Decode w b → Decode w a Source #

(<!)Decode w1 (a → t) → Decode ('Closed w) a → Decode w1 t infixl 4 Source #

Infix form of ApplyD with the same infixity and precedence as ($).

(<*!)Decode w1 (Annotator (a → t)) → Decode ('Closed d) (Annotator a) → Decode w1 (Annotator t) infixl 4 Source #

Infix form of ApplyAnn with the same infixity and precedence as ($).

(<?)Decode w1 (a → Either String t) → Decode ('Closed d) a → Decode w1 t infixl 4 Source #

Infix form of ApplyErr with the same infixity and precedence as ($).

decodeDecode w t → Decoder s t Source #

decodeSparseTypeable a ⇒ String → a → (WordField a) → [(Word, String)] → Decoder s a Source #

Index types for well-formed Coders.

Some CBOR instances wrap encoding sequences with prefixes and suffixes. I.e. prefix , encode, encode, encode , ... , suffix. There are two kinds of wrapping coders: Nary sums, and Sparsely encoded products. Coders in these classes can only be decoded when they are wrapped by their closing forms Summands and SparseKeyed. Another dimension, where we use indexes to maintain type safety, are records which can be encoded densely (all their fields serialised) or sparsely (only some of their fields). We use indexes to types to try and mark (and enforce) these distinctions.

data Density Source #

Index for record density. Distinguishing (all the fields) from (some of the fields).

Constructors

Dense 
Sparse 

data Wrapped where Source #

Index for a wrapped Coder. Wrapping is necessary for Summands and SparseKeyed.

Constructors

OpenWrapped 
ClosedDensityWrapped 

data Field t where Source #

A Field pairs an update function and a decoder for one field of a Sparse record.

Constructors

Field ∷ (x → t → t) → (∀ s. Decoder s x) → Field t 

ofield ∷ (StrictMaybe x → t → t) → Decode ('Closed d) x → Field t Source #

invalidField ∷ ∀ t. WordField t Source #

field ∷ (x → t → t) → Decode ('Closed d) x → Field t Source #

fieldGuarded Source #

Arguments

String

The message to use if the condition fails

→ (x → Bool)

The condition to guard against

→ (x → t → t) 
Decode ('Closed d) x 
Field t 

fieldAApplicative ann ⇒ (x → t → t) → Decode ('Closed d) x → Field (ann t) Source #

Sparse decode something with a (DecCBOR (Annotator t)) instance A special case of field

fieldAAApplicative ann ⇒ (x → t → t) → Decode ('Closed d) (ann x) → Field (ann t) Source #

Sparse decode something with a (DecCBOR (Annotator t)) instance

Using Duals

decodeDual ∷ ∀ t. (EncCBOR t, DecCBOR t) ⇒ Decode ('Closed 'Dense) t Source #

Use encodeDual and decodeDual, when you want to guarantee that a type has both EncCBOR and FromCBR instances.

Containers, Combinators

Low level (Encoding/Decoder) utility functions

decodeRecordNamedText → (a → Int) → Decoder s a → Decoder s a Source #

decodeRecordNamedT ∷ (MonadTrans m, Monad (m (Decoder s))) ⇒ Text → (a → Int) → m (Decoder s) a → m (Decoder s) a Source #

decodeRecordSumText → (WordDecoder s (Int, a)) → Decoder s a Source #

invalidKeyMonadFail m ⇒ Word → m a Source #

Report an error when a numeric key of the type constructor doesn't match.

guardUntilAtLeastDecCBOR a ⇒ StringVersionDecode ('Closed 'Dense) a Source #

Prevent decoding until the Version is at least the provided version.