@@@@@@@@ @@@@@@ @@@ @@@
@@! @@! @@@ @@!@!@@@
@!!!:! @!@ !@! @!@@!!@!
!!: !!: !!! !!: !!!
: :: ::: : :. : :: :
Erlang Object Notation
In an Erlang system, a dictionary-style datastructure should be used to pass data between subsystems. This library supports that observation in two ways.
The eon
module exports a clean API for working with dictionaries
(internally referred to as "objects").
- Basics:
new
,get
,set
,del
; - Higher order:
map
,filter
,fold
; - Sets:
union
,intersection
,difference
; - Iterators:
make
,next
,done
.
Most functions take the object as the first argument
(mnemonic: set(Obj, K, V) --> Obj[K] = V
).
All API functions support both common implementations of the dictionary interface in Erlang (lists of two-tuples aka proplists and dicts) as inputs and promote their outputs to dicts.
EON also supports a third representation, lists with an even number of elements where the odd elements are interpreted as keys and the even elements are interpreted as values. We call these "literals". Example:
The following terms are equivalent as far as EON is concerned:
dict:store(k2, v2, dict:store(k1, v1, dict:new()))
[ {k1, v1}
, {k2, v2}
]
[ k1, v1
, k2, v2
]
The `eon_type1 module supports checking EON objects (dicts) against a type declaration.
The API is eon_type:check_obj(Obj, Decl)
where both Obj
and Decl
are
EON objects (proplists or dicts). Obj
and Decl
must have the same
keys. Obj
maps those keys to arbitrary terms whereas Decl
maps them
to types. A type is simply an atom, the name of a module implementing
one of the eon_type_
behaviours.
There are two eon_type_
behaviours:
Requires implementations of 5 callback functions:
name/0
returns the name of the type (for error reporting).parameters/0
returns the parameters needed by the type (see below).normalize/2
takes the term being checked and the current parameters and returns the term, possibly after applying normalization of some sort to it. This callback is usually the identity function (but can come in handy when checking external data, which is often encoded as strings).validate/2
takes the term being checked and the current parameters and returns true iff the term is a member of this type (crash or return false otherwise). This is the central validation callback.convert/2
takes the term being checked and the current parameters and returns the term, possibly after transforming it to an internal representation. This is the central conversion callback.
Requires implementations of 3 callback functions.
name/0
as above.parameters/0
as above.decl/2
takes the term being checked and the current parameters and returns a decl object (a specification of what the term, which implicitly must be an EON object, should look like, see above). This is the central recursion callback.
All EON types take parameters. These are arbitrary key/value mappings
which provide external context to the various callbacks (for instance,
the validate/2
callback for a phone number may need to know which
country the phone number is supposed to be legal for).
The parameters/0
callbacks return a list of parameter names:
parameters() -> []. %no params
parameters() -> [foo, bar] %two params, foo and bar
parameters() -> [foo, {bar, 42}] %two params, foo and bar
%bar defaults to 42
Parameters are represented as EON objects, so the callback functions'
second argument will be a dict mapping all the names in the list
returned by the type's parameters/0
callback to terms.
There are three kinds of parameters values.
- Explicit parameter values are passed in the decl object: instead of just
mapping a key to a type (atom) the decl object may map it to a tuple,
{Type, Args}
, whereArgs
is an object which provides mappings for a subset ofType
's parameters. - Implicit parameter values are extracted from the decl object a type occurs in. If there's a key which matches the name of a parameter and no explicit value has been provided for this parameter, the value associated with this key after type checking (and conversion!) will be passed as the value of the parameter.
- Default parameter values are provided in the paramters callback (see example above).
Under-parameterized types are untypable.
The EON type checker takes the input object and the decl object and
returns the converted (original values are replaced by the output of
the primitive types' convert/2
callbacks) version of the input object
if and only if all fields can be validated (the primitive types' validate/2
callbacks return true).
It does this by computing the fixpoint of the input object under the parameter-constraints imposed by the decl object. Progress occurs when either
- a new field can be validated because we have all parameters for its type, or
- we find a new implicit parameter (which must have been validated already).
This probably sounds more complicated than it is. Have a look at
eon_type.erl
!
alias types
list types
sum types
check_term
&c.
eon.erl -- dict API
eon.hrl -- shared typedefs
eon_type.erl -- type checker
eon_type_alias.erl -- behaviour for alias types
eon_type_list.erl -- behaviour for list types
eon_type_prim.erl -- behaviour for primitive types
eon_type_rec.erl -- behaviour for recursive types