Terms

Web data, quoted statements and invented names, without any new syntax.

terms.py · output · proof · check · try it in the playground


The question

Data on the web (the Semantic Web, with its data model RDF) is made of small statements called triples: subject, predicate, object, such as “this page, has title, hello”. Names are web addresses (IRIs), and text can carry a language tag (“hello”, in English).

Sometimes you also want to talk about a group of statements without claiming they are true: a quoted graph, like quoting someone’s words.

Can a small rule language handle all this without special features, and what does it conclude about the quoted graph?


What we tell eyekind: the data

Everything is written as ordinary nested terms, names with parts in brackets:

fact(
  quoted(
    graph(
      [
        triple(
          iri('https://example.org/s'),
          iri('https://example.org/p'),
          literal('hello', lang('en')),
        ),
      ],
    ),
  ),
)

Read from the inside out:


What we tell eyekind: the rules

# List membership, and a triple's membership of a graph.
fact(member_of(X, [X, *_]))
implied_by(member_of(X, [_, *Xs]), member_of(X, Xs))
implied_by(includes(graph(Triples), Triple), member_of(Triple, Triples))
implies([quoted(G), includes(G, T)], found(T))
# W occurs only in the head: one fresh Skolem term per quoted graph.
implies(quoted(X), witness(X, W))

Rules match these terms by unification: lining two terms up part by part and filling in variables so they become equal. That works for web data as for any other data; eyekind has no special support for it.


What eyekind concludes

// Conclusions: the facts the program derives, in the order derived.
found(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))))
witness(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), skolem(6, 'W', [graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])]))

The program has no query, so eyekind lists every fact it derived.


Naming what a rule says exists

The witness rule says: for every quoted graph X, there is some W. It never says which. To write a conclusion down, eyekind needs a name for that something, so it makes one up: a Skolem term (named after the logician Thoralf Skolem).

skolem(6, 'W', [the graph]) is built from three parts:

So it reads “the W that rule 6 says exists for this graph”. The same rule used for the same graph always gives the same name, and a different graph would give a different one. That is why a checker can recompute it.


The proof, line by line

eyekind --proof writes down how it reached each conclusion. This is the complete proof:

// Conclusions: the facts the program derives, in the order derived.
found(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))))
witness(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), skolem(6, 'W', [graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])]))

// Clauses: the program clauses the steps cite, by clause number.
clause(1, fact(quoted(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]))))
clause(2, fact(member_of(X, [X, ..._0])))
clause(4, implied_by(includes(graph(Triples), Triple), member_of(Triple, Triples)))
clause(5, implies([quoted(G), includes(G, T)], found(T)))
clause(6, implies(quoted(X), witness(X, W)))

// Steps: each goal, what justifies it (a clause, a builtin, a control, or an
// absence or collection taken on trust), the bindings and the goals it uses.
step(found(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))), clause(5), {G: graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), T: triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))}, [quoted(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])), includes(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))))])
step(quoted(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])), clause(1), {}, [])
step(includes(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))), clause(4), {Triples: [triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))], Triple: triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))}, [member_of(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))), [triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])])
step(member_of(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))), [triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), clause(2), {X: triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))), _0: []}, [])
step(witness(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), skolem(6, 'W', [graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])])), clause(6), {X: graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), W: skolem(6, 'W', [graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])])}, [quoted(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]))])

The lines are long because the whole triple is written out each time. It has three parts:

Write the graph for the quoted graph and the triple for the “hello” triple, and the steps read:

  1. The triple is found — by rule 5, with G = the graph and T = the triple, because the graph is quoted and includes the triple.
  2. The graph is quoted — fact 1.
  3. The graph includes the triple — by rule 4, because the triple is a member of the graph’s list.
  4. The triple is a member of that list — fact 2: it is the first item, and the rest of the list (_0) is empty, [].
  5. There is a witness for the graph, named skolem(6, 'W', [the graph]) — by rule 6, with X = the graph and W = that invented name, because the graph is quoted (step 2, reused).

Checked: what the report says

A proof is only worth something if someone else can check it. eyekind -c generates the proof and checks it against the program; --check-proof checks a saved proof. The checker never reasons by itself: it only confirms each step against the program. This is the complete report:

// Checked answers: these follow from the input program.
found(triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en'))))
witness(graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))]), skolem(6, 'W', [graph([triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hello', lang('en')))])]))

// C1-C8: resolution, acyclicity, justification, coverage,
// re-decision, boundary consistency, relevance and witness integrity.
condition('C1', 'resolution', 'ok', 5)
condition('C2', 'well_founded', 'ok', 5)
condition('C3', 'justification', 'ok', 5)
condition('C4', 'coverage', 'ok', 6)
condition('C5', 're_decision', 'ok', 0)
condition('C6', 'boundary_consistency', 'ok', 0)
condition('C7', 'relevance', 'ok', 7)
condition('C8', 'witness_integrity', 'ok', 1)

// Proof statistics: recorded steps and how they were checked.
steps(5)
verified(5)
recomputed(0)
composed(0)
trusted(0)
claims(2)

// This verdict applies to the claims above; it does not establish completeness.
verdict('checked')

Each condition line names a check, says 'ok', and counts what it looked at:

The statistics: 5 steps, all 5 verified against the program, none taken on trust, 2 claims. The verdict is checked: both conclusions follow from the program. As the last comment says, this confirms the claims; it does not say the program has no other conclusions.


Try it

eyekind examples/terms.py            # the answers
eyekind --proof examples/terms.py    # answers with their proof

Or open it in the playground. Add a second triple to the list, for example triple(iri('https://example.org/s'), iri('https://example.org/p'), literal('hallo', lang('nl'))), and run again: two found lines appear, one for “hello” in English and one for “hallo” in Dutch. There is still one witness, because there is still one quoted graph, but its Skolem term now holds the larger graph.


Takeaway

Web addresses, language-tagged text, statements and quotations all fit into plain terms, so the same simple, checkable reasoning works on them.