Encoder and Decoder
Encoder and Decoder each name every scalar the language has - bool, int, unsigned, float, decimal, string, bytes - plus the four shapes a value can take, sequence, map, record and variant, which open and are closed by finish.
A format implements Encoder and Decoder, the two traits behind Encode and Decode, and
never sees a type. Both name the same small vocabulary: one method per scalar the language has, and four shapes
for everything built out of them. A schema format implements Describer, which has the same method names without
values.
Example#
type Point {
x: Int
y: Int
}
extend Point with Encode {
fn encode<Target: Encoder>(var target: Target) {
target.record "app/Point"
target.field "x"
x.encode target
target.field "y"
y.encode target
target.finish()
}
}
print Json().encode(Point(1, 2))Syntax#
trait Encoder {
var fn nothing()
var fn bool(value: Bool)
var fn int(value: Int64)
var fn unsigned(value: UInt64)
var fn float(value: Float64)
var fn decimal(value: Decimal)
var fn string(value: String)
var fn bytes(value: Bytes)
var fn sequence(length: Int?)
var fn map(length: Int?)
var fn record(typeName: String)
var fn variant(typeName: String, name: String)
var fn field(name: String)
var fn finish()
}Rules#
Encoderhas one method per scalar the language has, plusnothing.bool,int,unsigned,float,decimal,stringandbytesare the whole vocabulary; a format never invents a method for a type of its own.A narrow number travels widened.
Int8,Int16andInt32callintwith anInt64, so a format implements the wide method once; on the way back a value that does not fit is aDecodeError, not a silent truncation.Four shapes cover everything built from scalars, and
finishcloses the innermost one.sequenceandmapare followed by their items (a map's as a key and then its value),recordby its fields, each announced withfieldand followed by its value, andvariantis a record that also carries the case's name. A record that announces no field at all is a wrapper around the one value it holds, and a text format writes that value without anything around it.The format is a bound, not a trait type.
encode<Target: Encoder>(var target: Target)is monomorphized per format, so a format hands its own encoder over as avarpath and reads its result out of it afterwards, and the whole call chain is direct.DecodermirrorsEncoder, method for method, but reading is driven by the type. Each scalar method answers aResult<Value, DecodeError>instead of taking a value, because the type asks for what it expects and the input might not have it.field(name)answersfalsewhere the input has no such field, and the field's default applies.Decoder.nothing()answers aBool, not a value. This is howOptiondecides betweenNoneandSome: consuming a "nothing" if there is one, before ever asking for the value it would otherwise contain.hasNext()both tests and positions. It answerstruewhile one more item of the open sequence or one more key or value of the open map follows, and the next read takes it.
What this is not#
An Encoder is not a tree builder. A format writes what it is given as it is given it - there is no intermediate
document a format assembles and then serializes, which is what lets a sequence of a million items stream through in
constant memory. Values, the encoder that builds an EncodedValue, is the one format whose output is a tree, and a
program uses it only where it wants a value without its type.
type Pair {
first: Int
second: Int
}
extend Pair with Encode {
fn encode<Target: Encoder>(var target: Target) {
target.sequence Some(2)
first.encode target
second.encode target
target.finish()
}
}
print Json().encode(Pair(1, 2))type Pair {
first: Int
second: Int
}
extend Pair with Encode {
fn encode<Target: Encoder>(var target: Target) {
target.first
}
}
// error: `Target` has no member `first`Related#
- Encode and Decode - the generated forms most types never write by hand.
- There is no reflection - why a format never sees a type.
- std/encoding - the vocabulary,
EncodedValue,Structureand the steps a derived form is made of.