First-class relations
Area: Relations Teaches: relations are first-class constructs — n-ary, with their own properties, cardinality, specialization, and rule participation. Prerequisites: concepts and
<:(see concepts and hierarchies). Run:ox build examples/first_class_relations && ox query examples/first_class_relations
In most data languages a relationship is a second-class thing: a foreign key with no identity and no data of its own, or something you reify by hand into a stand-in node. Argon makes a relation a construct, declared with a rel keyword exactly as a concept is declared with a type keyword. An edge has the same standing as a node.
What to read in root.ar
A relation carries its own data. An employment has a salary — the salary belongs to the relationship, not to the person or the org:
pub rel Employment(employee: Person, employer: Org) { mut salary: Int };
Cardinality is per endpoint (UML association-end multiplicity — the i-th bracket bounds the distinct position-i values for a fixed combination of the other endpoints): [0..1] on the owner slot means an asset has at most one owner, and [0..*] on the asset slot means a person may own any number.
pub rel Owns(owner: Person, asset: Asset) [0..1] [0..*];
Relations are n-ary, not just binary — a sale relates three participants without an intermediate node:
pub rel Sale(seller: Person, buyer: Person, item: Asset);
Relations specialize, like concepts — every internship is an employment:
pub rel Internship(employee: Person, employer: Org) <: Employment;
Relations participate in rules, read in a rule body exactly as a concept’s extent is:
pub derive colleagues(a: Person, b: Person) :- Employment(a, o), Employment(b, o);
Running it
With Alice and Bob both employed by Acme, colleague_pairs returns the four pairings the rule derives — including each person with themselves, since the rule as written does not exclude a = b (adding an inequality guard is a natural exercise).
Honest caveats (what runs today)
- Maximum cardinality is enforced at the write path; a minimum above zero (e.g.
[1..1]) is recorded on the wire but not yet enforced in v0 —ox checkemitsOW1342naming the relation and position. This example uses[0..1]/[0..*], so it has no unenforced minimum.
This example is compiled and run in CI — the corpus auto-discovery parses and resolves it, and a corpus test (oxc-runtime/tests/examples_corpus.rs) pins the colleague_pairs behaviour to exactly the four pairings the rule derives. If the language changes underneath it, the build breaks rather than the docs going stale.