Reasoning about a rule

The rule itself can be evidence.

rule-inspection.js · output · proof · check · playground


The question

Usually a program uses its rules to draw conclusions. Sometimes we want to talk about a rule instead: “which rules does this program contain, and what do they say?” The program here has one ordinary rule: anything that is a is also c.

Can a second rule look that rule up and report it, keeping the link between its two halves?


What we tell eyemind

fact(a('b'))
implies(a(X), c(X))

implies(implies(X, Y), test(X, Y))
query(test(X, Y))

fact(...) states something true: b is a. implies(premise, conclusion) is a forward rule: whenever the premise holds, eyemind concludes the conclusion. Capitalized names such as X are variables, placeholders that stand for any value. So line 2 reads “whatever X is, if X is a then X is c.”

The third line is the unusual one. Its premise is itself implies(X, Y). Inside a premise, implies does not prove anything: it looks up the forward rules written in the program, and sets X to a rule’s premise and Y to its conclusion. This is called rule inspection. (implied_by(Head, Body) does the same for backward rules.)

query(test(X, Y)) asks eyemind to report every test(...) it can establish.


What eyemind concludes

// Answers: each instance of a query() that holds.
test(a(A), c(A))

The first line is a comment saying what follows. The answer is the rule from line 2 written as data: its premise a(A) and its conclusion c(A). The same A appears in both halves, so the answer still says “the same thing” on both sides: the link between premise and conclusion is kept. The A is a fresh variable, a copy of the rule’s own X.

Forward reasoning also derives c('b') from the first two lines, but the query only asks for test(...). Inspection does not need the fact a('b'): it reads the rule, not the facts.


Which rules can be inspected

The inspecting rule could in principle find itself, which would make no sense. To avoid that, eyemind only offers a fixed list of ordinary rules written in the program. Facts, queries, constraints, rules that eyemind generates internally, and rules that themselves inspect rules are left out. So here the only rule the third line can find is line 2. Each time a rule is inspected its variables are copied fresh, so two inspections never get tangled up.


The proof, line by line

eyemind --proof writes down how it reached each answer. This is the complete proof:

// Answers: each instance of a query() that holds.
test(a(A), c(A))

// Clauses: the program clauses the steps cite, by clause number.
clause(2, implies(a(X), c(X)))
clause(3, implies(implies(X, Y), test(X, Y)))

// Steps: each goal, what justifies it (a clause, a builtin, a control, an
// absence or a collection), the bindings and the goals it uses.
step(test(a(A), c(A)), clause(3), {X: a(A), Y: c(A)}, [implies(a(A), c(A))])
step(implies(a(A), c(A)), source(2), {}, [])

It has three parts:

Read the steps as sentences:

  1. test(a(A), c(A)) holds — by clause 3, with X being a(A) and Y being c(A), because the program contains the rule implies(a(A), c(A)).
  2. The program contains the rule implies(a(A), c(A)) — justified by source(2): this is not a fact or a calculation but a quotation of clause 2 of the program itself. It uses nothing else.

Checked: what the report says

A proof is only worth something if someone else can check it. eyemind -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:

// Answers checked subject to the obligations below.
test(a(A), c(A))

// C1-C10: resolution, acyclicity, justification, coverage, re-decision,
// boundary consistency, relevance, witness integrity, search closure and call fidelity.
condition('C1', 'resolution', 'ok', 2)
condition('C2', 'well_founded', 'ok', 2)
condition('C3', 'justification', 'ok', 2)
condition('C4', 'coverage', 'ok', 2)
condition('C5', 're_decision', 'ok', 0)
condition('C6', 'boundary_consistency', 'ok', 0)
condition('C7', 'relevance', 'ok', 3)
condition('C8', 'witness_integrity', 'ok', 0)
condition('C9', 'search_closure', 'ok', 0)
condition('C10', 'call_fidelity', 'ok', 0)

// Obligations: absence, collection or original-call evidence remains unproved.
obligation('call_fidelity', 'original_call', test(a(A), c(A)))

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

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

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

The statistics repeat this: 2 steps, both verified against the program, none taken on trust. The verdict is checked with obligations: the reported rule really is in the program, and the answer follows from it. It does not say the program has no other answers.

C10 leaves 1 original-call claim unproved by the available evidence. The verdict is checked with obligations: the recorded derivations are conditional on these call-fidelity obligations. --strict-proof rejects them. The report does not establish answer completeness.


Try it

eyemind examples/rule-inspection.js
eyemind --proof examples/rule-inspection.js
eyemind -c examples/rule-inspection.js
eyemind --check-proof examples/proof/rule-inspection.js examples/rule-inspection.js

Replace c(X) with d(X) in the ordinary rule: the answer becomes test(a(A), d(A)), following the rule as written. Delete the fact a('b'): the answer stays test(a(A), c(A)), because inspection reads the rule, not the facts. Add a second rule implies(b(X), d(X)): the query then reports both rules, test(a(A), c(A)) and test(b(B), d(B)).


Takeaway

Rules are not only instructions; they can also be facts to look at. When a rule reports on another rule, the proof points to the exact rule it read, and the checker confirms the report matches it.