A relationship defined as a chain of others, and the links that follow from it.
owl-property-chain.js · output · proof · check · try it in the playground
The question
A family tree records only who is whose parent: Alice is Bob’s parent, Bob is Carol’s, and Carol is Dave’s.
Nobody wrote down grandparents. But a grandparent is just a parent of a parent. The web’s ontology language OWL calls such a definition a property chain: one relationship spelled out as a sequence of others.
Given the chain, who is whose grandparent? And can each answer show the links it walked?
What we tell eyecode: the data
// grandparent is the chain parent, parent.
fact(property_chain('grandparent', ['parent', 'parent']))
fact(triple('alice', 'parent', 'bob'))
fact(triple('bob', 'parent', 'carol'))
fact(triple('carol', 'parent', 'dave'))
A relationship is written as a triple: subject, property, object. The chain is a plain list of property names, so it is data, just like the triples.
What we tell eyecode: following a chain
implies(
[property_chain(P, [First, ...Rest]), triple(S, First, M), follow_chain(Rest, M, E)],
triple(S, P, E),
)
fact(follow_chain([], N, N))
implied_by(follow_chain([P, ...Rest], S, E), [triple(S, P, M), follow_chain(Rest, M, E)])
query(triple(Person, 'grandparent', Grandchild))
- The
impliesrule takes the first property of a chain, finds a first link, and asksfollow_chainto walk the rest. follow_chaintakes one link per remaining property; an empty list means “you have arrived”.[First, ...Rest]splits a list into its first item and the rest. Each step shortens the list, so the walk always ends.
Nothing in the rules mentions grandparent or parent: they work for any
declared chain, of any length.
What eyecode concludes
triple('alice', 'grandparent', 'carol')
triple('bob', 'grandparent', 'dave')
Two grandparent links. Alice and Dave are three parent links apart, so Alice is not concluded to be Dave’s grandparent.
Why: the proof in plain words
For Alice and Carol:
- The chain for
grandparentisparent,parent— fact 1. - Alice is Bob’s parent — fact 2 — the first link.
- Bob is Carol’s parent — fact 3 — so from Bob, one
parentlink reaches Carol — rule 7 — and then the empty rest of the chain leaves us at Carol — fact 6. - So Alice is Carol’s grandparent — rule 5.
Bob and Dave go the same way, through facts 3 and 4.
Checked, not just claimed
A separate checker read all 10 proof steps against the program and confirmed that:
- each step is an exact instance of the rule or fact it cites (10 of 10);
- no conclusion depends on itself in a circle;
- every step serves one of the 2 conclusions, and nothing extra is included.
Verdict: checked. Nothing taken on trust.
Try it
eyecode examples/owl-property-chain.js # the grandparents
eyecode --proof examples/owl-property-chain.js # with their proof
Or open it in the playground.
Declare a longer chain and ask for it:
fact(property_chain('great_grandparent', ['parent', 'parent', 'parent']))
query(triple(Person, 'great_grandparent', G))
Run again: Alice is now also Dave’s great-grandparent, with no new rule.
The chain ['parent', 'grandparent'] gives the same answer, since a chain
can use a relationship that was itself derived.
Takeaway
When a definition is data rather than code, one general rule covers every definition of its kind. Adding a relationship means adding one line, and every link it produces still comes with the exact path that was walked.
