Schemas¶
When working in a larger project, when multiple people are involved, as many automatic sanity checks should be added to a code base as possible. Static typing, or its cousin in data specifications, schemas, are one of the tools we have to reduce mistakes and frustration.
Especially when the GCL is going to be processed in a dynamically typed language like Python where errors can be delayed for a long time (and writing explicit type checks is cumbersome and often omitted) validating the input data as quickly as possible makes sense.
Use cases¶
Typically, we want to guard against the following two scenarios:
- Missing (required) fields; and
- Values of incorrect types provided.
Schemas annotations are also used for automatic extraction of relevant data: a common use of GCL is as a preprocessor for generating JSON. However, typically you’ll have additional fields in your GCL tuples that are used for abstraction, which you don’t want to have end up in the JSON. We’ll use the schema to extract only the relevant fields from your objects.
Specifying a schema¶
Schemas are specified in GCL itself. Typically, you’d put them on tuples are that are designed to be mixed in with other tuples, to prevent them from being misused. For example, you can specify the types of input keys, or force downstream users to provide particular keys.
Scalars¶
Schema specification for scalars looks like this:
Human = {
hands : required int;
fingers = 5 * hands;
};
lancelot = Human {
hands = 2;
};
When mixing the tuple Human
into another tuple, a value for hands
must be
specified, and it must be an integer. Failing to provide that key will throw an
error when the tuple is accessed. The following built-in scalar types are
defined:
string
int
float
bool
null
Lists¶
Specifying that a key must be a list looks is done by specifying an example type
using []
, optionally containing a schema for the elements:
Human = {
names : required [string];
inventory : [];
};
lancelot = Human {
names = ['Sir', 'Lancelot', 'du', 'Lac'];
inventory = ['armor', { type = 'cat'; name = 'Peewee' }];
};
Tuples¶
To specify that a key must be a tuple (even a tuple with a specific type), specify an example tuple with the expected schema, OR even just refer to an exising tuple with the intended schema:
Human = {
name : required string;
father : Human;
mother : Human;
children : [Human];
};
lancelot = Human {
father = { name = 'King Bang' };
children = [galahad];
};
galahad = Human {
father = lancelot;
mother = { name = 'Elaine' };
};
Controlling key visibility on exports¶
Sometimes you’re building a model that you want to export to another program. A typical example would be to use GCL as a preprocessor to have a deep model with lots of abstraction, “compiling down” to a flat list of easy to process objects stored in a JSON file (for example for a single page web app).
Some keys in tuples will be intended for the final output, and some are just to
input keys for the abstraction mechanisms. By marking a key as private
, it
will not be exported when calling to_python
.
For example, given the model from before:
Human = {
hands : private required int;
fingers = 5 * hands;
};
lancelot = Human {
hands = 2;
};
hands
is only an implementation detail. We’re actually only interested in the
number of fingers that Lancelot has. By running gcl2json
on this model:
$ gcl2json knights.gcl lancelot
{"lancelot": {"fingers": 10}}
hands
is nowhere to be found!