plan: design parameterised relations — Slice 6 (role signature: a+b+c) & Slice 7 (algebra: d)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 21s
Capture the Relation<…> design from the discussion. The reframe: the parameters split into two halves — the role SIGNATURE (shape of a tuple: per-role type a, arity b, cardinality c) and the relation's ALGEBRA (behaviour: transitivity/symmetry/inverse/ sub-relations d). A relation is Relation<signature>; today's binary typed relations are the degenerate 2-role case. Slice 6: generalise :rel to a :roles signature; (a) per-role type = the declares-anchor made explicit, (b) arity needs reification (instance-posts) for n-ary, (c) cardinality by counting. Nominal variance, JIT caveat for n-ary role iteration. Slice 7: declared algebraic properties with GENERIC closure (retires the hardcoded is-a/subtype closure — OWL property characteristics); real inverse relations; sub-relations. Decidable core stops here; defined-by-rule + cross-role predicates fenced behind the predicate-language decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,58 @@ relation-subtype closure when relations get subtyped; the boot title cache above
|
||||
- FUTURE: arbitrary predicate constraints (not just required blocks); constraints as their
|
||||
own posts; relation cardinality (`is-a` single-valued?) as a declared constraint.
|
||||
|
||||
## Parameterised relations (DESIGN — Slices 6 & 7)
|
||||
|
||||
The next axis: `Relation<…>`. The key reframe is that the obvious parameters aren't separate
|
||||
`<N>`s — they split into **two halves**, and they compose into one coherent thing:
|
||||
|
||||
1. **The role SIGNATURE** (the *shape* of a tuple) — Slice 6 (a + b + c).
|
||||
2. **The relation's ALGEBRA** (how it *behaves*) — Slice 7 (d).
|
||||
|
||||
A relation is `Relation<signature>`, where a signature is an ordered list of **roles**, each
|
||||
role carrying a **type** and a **cardinality**; the signature's length is the **arity**.
|
||||
Today's binary typed relations are the degenerate 2-role case — backward-compatible, nothing
|
||||
gets thrown away. Prior art to borrow (and stay decidable within): Codd / ER reified
|
||||
relationships (signature), OWL property characteristics (algebra), Datalog / relation algebra
|
||||
(derived relations — the undecidable frontier; fence it). Decidability rule of thumb: concrete
|
||||
+ algebraic role-types and counts stay decidable; arbitrary predicates / recursive rules don't.
|
||||
|
||||
### Slice 6 — the role signature (a + b + c)
|
||||
Generalise the relation-post's `:rel` slot from `{:symmetric :label}` to a `:roles` list —
|
||||
`{:roles [{:name :type :card} …]}` — driving picker candidates, validation, and arity per-role:
|
||||
- **(a) per-role type** — each role's `:type` is a type-expr (so it can be algebraic:
|
||||
`Relation<Work ∧ Published>`). The object-role's type IS today's `declares`-anchor — make it
|
||||
explicit. `valid-object?` becomes per-role `is-a-expr?` against `:type`.
|
||||
- **(b) arity** = `(len roles)`. Binary stays the fast `src|kind|dst` edge path; **n-ary needs
|
||||
reification**: a relation *instance* becomes its own post with role edges (`subject→X`,
|
||||
`object→Y`, `recipient→Z`) — on-brand (we made relation *kinds* posts; now *instances* too),
|
||||
but a SECOND representation alongside the binary edges, not a tweak. Qualifiers (Wikidata-
|
||||
style) then come free as extra roles.
|
||||
- **(c) cardinality** — `:card` per role (min/max; functional = max 1, required = min 1),
|
||||
enforced on relate by counting. Composes with Slice 5 validation. No model change for binary.
|
||||
- Siblings: ordered roles (set vs list), keys/identity (which roles identify a tuple).
|
||||
- **Layering (cheapest → deepest):** (c) cardinality on the binary object-role → (a) explicit
|
||||
role-type + the 2-role signature abstraction → (b) reified n-ary (the real lift).
|
||||
- **Variance: nominal, none initially** — no structural subtyping of `Relation<…>` (covariance
|
||||
of parameterised types is a research project). JIT caveat: 2-role signatures are unrollable;
|
||||
n-ary role-iteration with per-role reads needs the cache/unroll treatment (Slice 2/5 lesson).
|
||||
|
||||
### Slice 7 — relation algebra / characteristics (d)
|
||||
The behaviour half — and (d) **transitivity** is special because we ALREADY hardcode it
|
||||
(`is-a`/`subtype-of` closure via lib/relations); declaring it generically *removes* code.
|
||||
- **Algebraic properties** declared on the relation-post (`:transitive :symmetric :reflexive
|
||||
:antisymmetric :irreflexive`), with the closure **derived generically** from them — OWL's
|
||||
property characteristics. `subtype-of` becomes "a declared transitive + antisymmetric
|
||||
relation" (a partial order), not a special case. `:symmetric` (already stored) folds in here.
|
||||
- **Inverse relations** — a real `:inverse` (not just the `:inverse-label` display hint):
|
||||
relating one auto-derives the converse, the way `:symmetric` writes both directions.
|
||||
- **Sub-relations** — relations subtyping relations (`wrote subPropertyOf created`): X wrote Y
|
||||
⟹ X created Y. Same `subtype-of` machinery, over the `relation` root — meta-circular.
|
||||
- **Decidable core stops here.** Beyond-d, FENCED: defined-by-rule relations (composition,
|
||||
`grandparent = parent ∘ parent` — straight onto the Datalog substrate, but gate to
|
||||
stratified/bounded rules) and cross-role refinement predicates (`start < end`) — both need
|
||||
the predicate-language-vs-embedded-code decision first.
|
||||
|
||||
## Open design questions (track as we go)
|
||||
1. **Subject-end declarations** — who may be the *source* of a relation (a root `Thing`?).
|
||||
2. **Inheritance path** — through `is-a` AND `subtype-of` downward (current choice); revisit
|
||||
|
||||
Reference in New Issue
Block a user