Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
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:
- Book-keeping details neccesary to write correct instances are hidden from the user.
- Inverse
EncCBOR
andDecCBOR
instances have visually similar definitions. - Advanced instances involving sparse-encoding, compact-representation, and
Annotator
instances are also supported.
A Guide to Visual inspection of Duality in Encode and Decode
(Sum c)
and(SumD c)
are duals(Rec c)
and(RecD c)
are duals(Keyed c)
and(KeyedD c)
are duals(OmitC x)
and(Emit x)
are duals(Omit p ..)
and(Emit x)
are duals if (p x) is True(To x)
and(From)
are duals if (x::T) and (forall (y::T). isRight (roundTrip y))(E enc x)
and(D dec)
are duals if (forall x . isRight (roundTrip' enc dec x))(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
- data Encode (w ∷ Wrapped) t where
- Rec ∷ t → Encode ('Closed 'Dense) t
- Sum ∷ t → Word → Encode 'Open t
- Keyed ∷ t → Encode ('Closed 'Sparse) t
- To ∷ EncCBOR a ⇒ a → Encode ('Closed 'Dense) a
- E ∷ (t → Encoding) → t → Encode ('Closed 'Dense) t
- MapE ∷ (a → b) → Encode w a → Encode w b
- OmitC ∷ t → Encode w t
- Tag ∷ Word → Encode ('Closed x) t → Encode ('Closed x) t
- Omit ∷ (t → Bool) → Encode ('Closed 'Sparse) t → Encode ('Closed 'Sparse) t
- Key ∷ Word → Encode ('Closed 'Dense) t → Encode ('Closed 'Sparse) t
- ApplyE ∷ Encode w (a → t) → Encode ('Closed r) a → Encode w t
- (!>) ∷ Encode w (a → t) → Encode ('Closed r) a → Encode w t
- encode ∷ Encode w t → Encoding
- runE ∷ Encode w t → t
- encodeDual ∷ ∀ t. (EncCBOR t, DecCBOR t) ⇒ t → Encode ('Closed 'Dense) t
- encodeKeyedStrictMaybeWith ∷ Word → (a → Encoding) → StrictMaybe a → Encode ('Closed 'Sparse) (StrictMaybe a)
- encodeKeyedStrictMaybe ∷ EncCBOR a ⇒ Word → StrictMaybe a → Encode ('Closed 'Sparse) (StrictMaybe a)
- data Decode (w ∷ Wrapped) t where
- RecD ∷ t → Decode ('Closed 'Dense) t
- SumD ∷ t → Decode 'Open t
- Summands ∷ Text → (Word → Decode 'Open t) → Decode ('Closed 'Dense) t
- SparseKeyed ∷ Typeable t ⇒ String → t → (Word → Field t) → [(Word, String)] → Decode ('Closed 'Dense) t
- KeyedD ∷ t → Decode ('Closed 'Sparse) t
- From ∷ DecCBOR t ⇒ Decode w t
- D ∷ (∀ s. Decoder s t) → Decode ('Closed 'Dense) t
- ApplyD ∷ Decode w1 (a → t) → Decode ('Closed d) a → Decode w1 t
- Invalid ∷ Word → Decode w t
- Map ∷ (a → b) → Decode w a → Decode w b
- TagD ∷ Word → Decode ('Closed x) t → Decode ('Closed x) t
- Emit ∷ t → Decode w t
- Ann ∷ Decode w t → Decode w (Annotator t)
- ApplyAnn ∷ Decode w1 (Annotator (a → t)) → Decode ('Closed d) (Annotator a) → Decode w1 (Annotator t)
- ApplyErr ∷ Decode w1 (a → Either String t) → Decode ('Closed d) a → Decode w1 t
- (<!) ∷ Decode w1 (a → t) → Decode ('Closed w) a → Decode w1 t
- (<*!) ∷ Decode w1 (Annotator (a → t)) → Decode ('Closed d) (Annotator a) → Decode w1 (Annotator t)
- (<?) ∷ Decode w1 (a → Either String t) → Decode ('Closed d) a → Decode w1 t
- decode ∷ Decode w t → Decoder s t
- decodeSparse ∷ Typeable a ⇒ String → a → (Word → Field a) → [(Word, String)] → Decoder s a
- data Density
- data Wrapped where
- data Field t where
- ofield ∷ (StrictMaybe x → t → t) → Decode ('Closed d) x → Field t
- invalidField ∷ ∀ t. Word → Field t
- field ∷ (x → t → t) → Decode ('Closed d) x → Field t
- fieldGuarded ∷ String → (x → Bool) → (x → t → t) → Decode ('Closed d) x → Field t
- fieldA ∷ Applicative ann ⇒ (x → t → t) → Decode ('Closed d) x → Field (ann t)
- fieldAA ∷ Applicative ann ⇒ (x → t → t) → Decode ('Closed d) (ann x) → Field (ann t)
- decodeDual ∷ ∀ t. (EncCBOR t, DecCBOR t) ⇒ Decode ('Closed 'Dense) t
- listDecodeA ∷ Decode ('Closed 'Dense) (Annotator x) → Decode ('Closed 'Dense) (Annotator [x])
- mapDecodeA ∷ Ord k ⇒ Decode ('Closed 'Dense) (Annotator k) → Decode ('Closed 'Dense) (Annotator v) → Decode ('Closed 'Dense) (Annotator (Map k v))
- setDecodeA ∷ Ord x ⇒ Decode ('Closed 'Dense) (Annotator x) → Decode ('Closed 'Dense) (Annotator (Set x))
- decodeRecordNamed ∷ Text → (a → Int) → Decoder s a → Decoder s a
- decodeRecordNamedT ∷ (MonadTrans m, Monad (m (Decoder s))) ⇒ Text → (a → Int) → m (Decoder s) a → m (Decoder s) a
- decodeRecordSum ∷ Text → (Word → Decoder s (Int, a)) → Decoder s a
- invalidKey ∷ MonadFail m ⇒ Word → m a
- unusedRequiredKeys ∷ Set Word → [(Word, String)] → String → Decoder s a
- duplicateKey ∷ String → Word → Decoder s a
- guardUntilAtLeast ∷ DecCBOR a ⇒ String → Version → Decode ('Closed 'Dense) a
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.
Rec ∷ t → Encode ('Closed 'Dense) t | Label the constructor of a Record-like datatype (one with exactly 1 constructor) as an Encode. |
Sum ∷ t → Word → Encode '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). |
To ∷ EncCBOR a ⇒ a → Encode ('Closed 'Dense) a | Label an (component, field, argument) to be encoded using an existing EncCBOR 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). |
Tag ∷ Word → Encode ('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. |
Key ∷ Word → Encode ('Closed 'Dense) t → Encode ('Closed 'Sparse) t | Precede the encoding (in the produced bytes) with the key Word. Analagous to |
ApplyE ∷ Encode w (a → t) → Encode ('Closed r) a → Encode w t | Apply a functional encoding (arising from |
(!>) ∷ 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 $
encode ∷ Encode w t → Encoding Source #
Translate a first-order @(Encode w d) domain specific langage program, into an Encoding
.
Index types for well-formed Coders.
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
encodeKeyedStrictMaybeWith ∷ Word → (a → Encoding) → StrictMaybe a → Encode ('Closed 'Sparse) (StrictMaybe a) Source #
encodeKeyedStrictMaybe ∷ EncCBOR a ⇒ Word → StrictMaybe a → Encode ('Closed 'Sparse) (StrictMaybe a) Source #
Creating decoders.
data Decode (w ∷ Wrapped) t where Source #
The type (
is designed to be dual to Decode
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 Encode
t)(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)
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. |
Summands ∷ Text → (Word → Decode '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 |
KeyedD ∷ t → Decode ('Closed 'Sparse) t | Label a (component, field, argument) as sparsely stored, which will be populated with the default value. |
From ∷ DecCBOR t ⇒ Decode w t | Label a (component, field, argument). It will be decoded using the existing
DecCBOR instance at |
D ∷ (∀ s. Decoder s t) → Decode ('Closed 'Dense) t | Label a (component, field, argument). It will be decoded using the given decoder. |
ApplyD ∷ Decode w1 (a → t) → Decode ('Closed d) a → Decode w1 t | Apply a functional decoding (arising from |
Invalid ∷ Word → Decode 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. |
TagD ∷ Word → Decode ('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 |
Ann ∷ Decode w t → Decode w (Annotator t) | Lift a |
ApplyAnn | Apply a functional decoding (arising from |
ApplyErr ∷ Decode 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
Applicative (Decode ('Closed d)) Source # | |
Defined in Cardano.Ledger.Binary.Decoding.Coders 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 # | |
(<!) ∷ 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 ($)
.
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.
Index for record density. Distinguishing (all the fields) from (some of the fields).
Index for a wrapped Coder. Wrapping is necessary for Summands
and SparseKeyed
.
A Field pairs an update function and a decoder for one field of a Sparse record.
invalidField ∷ ∀ t. Word → Field t Source #
fieldA ∷ Applicative 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
fieldAA ∷ Applicative 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
listDecodeA ∷ Decode ('Closed 'Dense) (Annotator x) → Decode ('Closed 'Dense) (Annotator [x]) Source #
mapDecodeA ∷ Ord k ⇒ Decode ('Closed 'Dense) (Annotator k) → Decode ('Closed 'Dense) (Annotator v) → Decode ('Closed 'Dense) (Annotator (Map k v)) Source #
setDecodeA ∷ Ord x ⇒ Decode ('Closed 'Dense) (Annotator x) → Decode ('Closed 'Dense) (Annotator (Set x)) Source #
Low level (Encoding/Decoder) utility functions
decodeRecordNamedT ∷ (MonadTrans m, Monad (m (Decoder s))) ⇒ Text → (a → Int) → m (Decoder s) a → m (Decoder s) a Source #
invalidKey ∷ MonadFail m ⇒ Word → m a Source #
Report an error when a numeric key of the type constructor doesn't match.