How Prolog corroborates the proof that Core Logic is not paraconsistent
Table of Contents
- 1. The running rules are exactly \(\mathcal{F}\)
- 2. The rules prove theorems, the failure is discriminating
- 3. The theorem, section by section, as
main.runs it- 3.1. Step 1 — \(\mathcal{F}\) proves the minimal theorems, and the two Claims fail
- 3.2. Step 2 — DNS.1 and DNS.2 are derived rules of \(\mathcal{F}\)
- 3.3. Step 3 — DNS.1 is invertible: two independent corroborations of the induction
- 3.4. Step 4 — from invertibility to \(\overline{DNS.1}\)
- 3.5. Step 5 — the contradiction (
theorem.) - 3.6. Step 6 — the corollary
- 4. One further door: permissive and strict modes bracket the result
- 5. What stays outside Prolog
- 6. Run it
- 7. References
Dedicated to Jan Wielemaker, whose SWI-Prolog — and its WebAssembly incarnation — make the executable model of this proof something a reader can run in a browser, with nothing to install.
The proof that Core Logic is not paraconsistent is certified three
times over — in Coq, in Lean 4 and in Athena — where a proof object is
checked against a small trusted kernel. The accompanying Prolog file,
core_logic_is_not_paraconsistent.pl, is a different kind of object:
it does not certify, it corroborates. This note says, precisely, why
the corroboration is worth having, by closing three doors
(corresponding to section 1, 2 and 3) an objector might try to keep
open. Door one: the five rules the Prolog engine runs are, line for
line, the five constructors of the Coq Inductive — and those five
are all of Table 1, the whole of the fragment \(\mathcal{F}\), with
nothing smuggled in. Door two: those rules prove real theorems of
minimal logic, including the very formula the anonymous correspondent
put forward — corroborated by the Prolog search and, on the same base,
kernel-certified by Coq with zero axioms — so the failure to derive
the two Claims is a discriminating failure, not the impotence of a
crippled encoding. Door three: the deductive step of the theorem is
reconstructed, section by section, exactly as the command main. runs
it — search, construction, and the two branches of the contradiction —
and every proof tree shown below is the one the machine emits, not a
hand-set copy.
Let me restate the epistemic frame in one paragraph, because the whole
value of the exercise depends on not overclaiming. Prolog is not a
theorem verifier. It is a logic-programming language with a fixed
SLD-resolution strategy, here programmed to act as a proof checker over
a bounded search. Its trust base is not a kernel but the entire
hand-written program, plus our reading of what its execution means.
Derivability is a bounded proof search (for(Th,0,6), deepening on
\(L\to\)); the two antisequents rest on the failure of that search
under the closed-world assumption. “No derivation of depth \(\le 6\) was
found” becomes “no derivation exists” only by an argument made outside
Prolog. That is corroboration in the strict sense: a claim that has
survived a genuine attempt at refutation, not a claim checked against a
kernel.
But corroboration of that kind still bites, for a reason the three assistants cannot match. Having no closed-world semantics, Coq, Lean and Athena cannot even attempt to refute the underivability of the Claims: they must posit each antisequent as a hypothesis — an axiom — and reason from it. The Prolog model does attempt the refutation, exhausts an iterative-deepening search over the finite fragment, and comes back empty-handed. Where the kernels assume the antisequent, the search tries and fails to break it. The two are complementary, not interchangeable. What follows shows the corroboration is not loose talk: each of its parts is a finite, exhibitable fact.
1. The running rules are exactly \(\mathcal{F}\)
The companion document Correspondence of the Fragment F rules already lines up Table 1 against Coq, Lean, Athena and Prolog, clause by clause. Here I only drive the nail: I take the Coq declaration and show, first, that the Prolog engine runs the same five rules, and second — the decisive point — that these five are all the rules there are.
The Coq derivability relation is one Inductive with exactly five
constructors:
Inductive derivable :
fragment_F -> set formula -> option formula -> Prop :=
| Ax : forall f G A, In A G -> derivable f G (Some A)
| L_neg : forall f G A, In (Neg A) G -> derivable f G (Some A) -> derivable f G None
| R_arrow : forall f G A B, derivable f (A :: G) (Some B) -> derivable f G (Some (Impl A B))
| L_arrow : forall f G A B C, In (Impl A B) G -> derivable f G (Some A) -> derivable f (B :: G) C -> derivable f G C
| R_arrow_core: forall G A B, derivable core_logic (A :: G) None -> derivable core_logic G (Some (Impl A B)).
The Prolog engine has one axiom clause and four rule clauses — one for each remaining constructor:
ax(Gamma:A) :- A \= (_=>_), A \= bot, iso_member(A, Gamma). % Ax
rules(lneg, Gamma:bot, Rest:A) :- iso_select(~A, Gamma, Rest). % L¬
rules(rcond, Gamma:A=>B, [A|Gamma]:B) :- B \= bot. % R→
rules(rcond_core, Gamma:A=>_B, [A|Gamma]:bot). % R→C
rules(lcond, Gamma:C, conj(Rest:A, [B|Rest]:C)) :- iso_select(A=>B, Gamma, Rest). % L→
Read the two side by side and the map is total and injective both ways:
ax ↔ Ax, lneg ↔ L_neg, rcond ↔ R_arrow, rcond_core ↔
R_arrow_core, lcond ↔ L_arrow. The membership form (iso_member,
iso_select) is Coq’s In A G to the letter; the empty succedent that
Coq writes None is the object constant bot in succedent position; a
conditional identity is derived, not initial, which is exactly why the
axiom clause guards A \ (=>)=. These are encoding conventions, set out
in the companion note, not extra rules.
The decisive move is the closure argument, and it belongs to the kind
of object an Inductive is. An inductive definition is closed: its
constructors are the only ways to build an inhabitant. So Coq’s five
constructors are not “five rules among others we happen to use” — they
are, provably, every way a sequent can be derived in \(\mathcal{F}\).
Print Assumptions confirms the definition is closed under the global
context. The Prolog side has, correspondingly, exactly five heads —
ax/1 plus four rules/3 clauses — and no more. There is nowhere for a
hidden rule to hide: an objector who suspects the underivability of the
Claims is an artifact of some smuggled-in principle can enumerate the
clauses and find that the list is closed, finite, and identical to
Table 1.
The first four rules are parametric in the fragment f (they hold
under the minimal reading \(\mathcal{F}_{\mathbf M}\) and the Core reading
\(\mathcal{F}_{\mathbb C}\) alike); only rcond_core is restricted to
core_logic. That single Core-specific rule is the whole of the extra
strength Core grants over the minimal fragment — and, as the theorem
shows, the whole of what breaks the symmetry on the fatal sequent.
2. The rules prove theorems, the failure is discriminating
A skeptic’s natural worry: perhaps the engine fails on \(\lnot A, A
\vdash B\) simply because it is too weak to prove anything interesting —
in which case the failure would be worthless. This door closes with a
single run. Take the formula the anonymous correspondent himself put
forward,
\[ (p \to (q \to r)) \to (q \to (p \to r)), \]
the permutation-of-antecedents theorem — a genuine theorem of minimal
(indeed of pure implicational) logic. Consult the file and click: the
engine answers PROVABLE and draws its tree — built, and rendered, by
the very clauses that compute it.
?- prove.
Two things about this derivation matter. First, it uses only ax,
rcond and lcond — that is, only Ax, \(R\to\) and \(L\to\). No
\(L\lnot\), and crucially no rcond_core: the theorem lives squarely
in the minimal fragment \(\mathcal{F}_{\mathbf M}\), with no appeal to the
one Core-specific rule and none whatever to ex falso. (One can check
this mechanically: the tree carries no \(L\lnot\) and no
\(R\to_{\mathbb C}\) step.) Second, note that the file’s fragment. command already
verifies the sequent form \(A\to(B\to C) \vdash B \to (A \to C)\); the
correspondent’s formula is just its fully-discharged, theorem-form
cousin. His challenge formula was, in essence, already in the test suite.
So the engine is not impotent: it proves the theorems minimal logic ought to prove, by the rules of \(\mathcal{F}\), and it does so using only the sub-fragment that excludes ex falso. When that same engine then fails on \(\lnot A, A \vdash B\), the failure is discriminating. It is not “this system proves nothing”; it is “this system proves the genuine theorems and refuses precisely ex-falso-shaped sequents” — which is exactly the behaviour that makes \(\mathcal{F}\) a fragment of minimal, and not of intuitionistic, logic.
2.1. The same theorem, certified: the Coq twin of the Prolog tree
It is worth pausing here, because this is the very formula whose earlier mis-encoding prompted us to correct the certifications. So let us put the two objects side by side, on the same formula, and see that they agree.
On the same three rules — Ax, \(R\to\), \(L\to\) — Coq certifies what
Prolog corroborated above. The calculus is not a second, look-alike
system: the formula, fragment_F and derivable declarations used
here are copied verbatim from Block A of the frozen Version-6
certification, and the code is byte-for-byte identical to it (same SHA256
on the declarations, comments aside). The theorem is proved for
arbitrary formulae \(p, q, r\), by exactly the shared minimal rules —
no L_neg, no R_arrow_core, no context_monotonicity:
Theorem exchange_weakening_regression :
forall p q r : formula,
derivable minimal_F []
(Some (Impl (Impl p (Impl q r)) (Impl q (Impl p r)))).
Proof.
intros p q r.
apply R_arrow. apply R_arrow. apply R_arrow.
apply L_arrow with (A := p) (B := Impl q r).
- simpl. right. right. left. reflexivity.
- apply Ax. simpl. left. reflexivity.
- apply L_arrow with (A := q) (B := r).
+ simpl. left. reflexivity.
+ apply Ax. simpl. right. right. left. reflexivity.
+ apply Ax. simpl. left. reflexivity.
Qed.
Print Assumptions exchange_weakening_regression.
(* Closed under the global context *)
The last line is the certification proper: Print Assumptions returns
Closed under the global context — the theorem rests on zero axioms.
(Note: this regression theorem is not part of Version 6 itself — the
frozen appendix does not need the objector’s formula; it is reproduced
here, on Version 6’s own base, purely as a companion check. The three
certifications are untouched.)
So the objector’s formula is settled twice over, in two proof languages that share nothing but the five rules of \(\mathcal{F}\):
- Coq certifies it — a proof object checked against the kernel, closed under the global context, zero axioms;
- Prolog corroborates it — a proof term found by search under the same three rules —
and the two derivations have the same shape, because it is the same
derivation. The one asymmetry is a bonus on the Prolog side: it does not
merely report success, it hands back the picture — the bussproofs
proof tree shown above is emitted by render_bussproofs/1 from the very
term it computed, ready to drop into the paper. Same theorem, same
fragment, no ex falso on either side; Coq gives the kernel-checked
certificate, Prolog gives the certificate’s portrait.
3. The theorem, section by section, as main. runs it
The command main. walks the article. I follow the same order, showing
at each step the machine’s own output, so that a reader can run the file
and read the paper in parallel.
3.1. Step 1 — \(\mathcal{F}\) proves the minimal theorems, and the two Claims fail
fragment. first confirms the standard minimal theorems (\(\vdash a \to
a\); \(a\to b, a \vdash b\); transitivity of \(\to\); \(\vdash a \to (b
\to a)\); permutation; \(\vdash \lnot a \to (a \to b)\)), then reports
the two Claims underivable:
~a, a |- b (Claim 1) : UNPROVABLE ~a, a |- ~b (Claim 2) : UNPROVABLE
This is the search failing to refute the antisequents — the first half of the corroboration.
3.2. Step 2 — DNS.1 and DNS.2 are derived rules of \(\mathcal{F}\)
dns_rules. builds the two derivations of the paper’s Table 2: DNS.1,
and DNS.2 — the right branch of the contradiction, and the one place the
Core-specific rule enters. Click to see each, exactly as the engine
emits it (\(\Delta = [\lnot a]\), \(A=a\), \(B=b\)):
?- dns1. ?- dns2.
Note where the Core-specific rule enters: \(R\to_{\mathbb C}\) discharges \(A\) against the empty succedent obtained from \(\lnot A, A \vdash\). This is the single point at which \(\mathcal{F}_{\mathbb C}\) does what \(\mathcal{F}_{\mathbf M}\) cannot, and it is what makes the DNS.2 conclusion derivable in \(\mathbb C\).
3.3. Step 3 — DNS.1 is invertible: two independent corroborations of the induction
The invertibility of DNS.1 is the pivot of the proof, and it is
established by structural induction on derivations — type-checked in both
Coq and Lean. The Prolog file adds two independent confirmations, by
routes those inductions do not take. First, dns1_sem_inv. verifies it
semantically, by a Quine-style truth-table reduction over the three
relevant valuations. Second, dns1_inv_cut. verifies it syntactically,
by an admissible Cut: since the contexts at issue are consistent —
Tennant’s own condition for Cut in \(\mathbb C\), and one that holds a
fortiori in \(\mathbf M\) — the inversion goes through as a single Cut on
the formula \((A\to B)\to B\), whose left premiss is a Slaney-style
double-negation introduction. Click to see that Cut derivation:
?- dns1_inv_cut.
Three roads — induction, truth tables, Cut — reach the same summit: corroboration by convergence, the same result re-derived by means that share no machinery with the certified one.
3.4. Step 4 — from invertibility to \(\overline{DNS.1}\)
dns1_anti. reads the invertibility as an equivalence of premiss and
conclusion, and takes its contrapositive: if the premiss is underivable,
so is the conclusion. That is the anti-rule \(\overline{DNS.1}\),
confirmed on concrete instances (e.g. \(a \nvdash b\) and \((a\to b)\to b
\nvdash b\)).
3.5. Step 5 — the contradiction (theorem.)
Now the two branches meet. theorem. confirms Claim 1 underivable,
confirms the DNS.2 conclusion derivable, and assembles the
contradiction square. Click to assemble it:
?- theorem.
Read the clash on the two occurrences of \(\lnot A, (A\to B)\to B \; ? \; B\): the left branch, from Claim 1 through \(\overline{DNS.1}\), says the sequent is not derivable; the right branch, through DNS.2, exhibits a derivation of it. A sequent both underivable and derived: that is the contradiction. The final bar is deliberately unlabelled — it is a metatheoretic clash of a sequent with its own refutation, not a natural-deduction negation elimination.
3.6. Step 6 — the corollary
corollary. runs the same machine with \(\lnot B\) in place of \(B\):
Claim 2 (\(\lnot A, A \vdash \lnot B\)) underivable, its DNS.2 conclusion
\((A \to \lnot B)\to \lnot B,\ \lnot A \vdash \lnot B\) derivable, same
clash, same \(\bot\).
?- corollary.
4. One further door: permissive and strict modes bracket the result
There is a last worry an objector could raise about the search itself:
the default engine (main.) uses permissive axioms that admit
weakening on the left at the leaves, for efficiency. Does that
permissiveness taint the verdicts? No — for two reasons.
First, the weakening is less un-Core than it looks. Left-weakening — adjoining an unused hypothesis to the context — is not a primitive rule of \(\mathbb C\), but it is Core-admissible in exactly the regime these sequents inhabit: so long as the context is not explicitly inconsistent and no derivation of \(\lnot A, A \vdash \lnot B\) is in play. Its propositional witness is that \(A \vdash B \to A\) is Core-provable — Tennant’s vacuous discharge, the diamond on the discharge stroke, which the strict run derives explicitly as \(B \vdash A \to B\) [1, p. 35]. The one regime where left-weakening would be genuinely illicit in Core is the inconsistent context, where unrestricted weakening would simply be ex falso — and that is exactly the case Core is built to refuse, and exactly the case the weakening-free strict run re-checks below. The permissive axiom is thus a matter of efficiency, not a smuggled-in non-Core principle.
Second, the two modes bracket the result from opposite sides. The
permissive engine proves a superset of what strict Core proves
(weakening only adds derivations). Therefore, for the negative
results, permissive failure is the stronger fact: if even the
weakening-admitting engine cannot derive \(\lnot A, A \vdash B\), then
strict Core — a fortiori — cannot. The over-approximation makes the
antisequents more robust, not less. For the positive results, one
wants the opposite guarantee, and core_strict. supplies it: with
strictly singleton axioms \([C]:C\), explicit context-splitting on
\(L\to\), and Tennant’s diamond on the discharge stroke (2017, p. 21), it
re-derives the load-bearing sequents without any weakening at all:
A |- A : DERIVABLE (strict Core OK) |- A => A : DERIVABLE (strict Core OK) A=>B, A |- B : DERIVABLE (strict Core OK) B |- A=>B (R-> vacuous) : DERIVABLE (strict Core OK) ~A, A |- (L~ on axiom) : DERIVABLE (strict Core OK) ~A |- A=>B (DNS.2 left premiss) : DERIVABLE (strict Core OK) (A=>B)=>B, ~A |- B (DNS.2 concl.) : DERIVABLE (strict Core OK) (A=>~B)=>~B, ~A |- ~B (Corollary) : DERIVABLE (strict Core OK) ~A, A |- B (Claim 1, must fail) : NOT DERIVABLE (as expected) ~A, A |- ~B (Claim 2, must fail) : NOT DERIVABLE (as expected)
Permissive search = an upper bound on provability, under which the Claims still fail; strict run = a lower bound, under which the positive derivations still succeed. The result holds at both ends. (The one line worth writing out for the reader who wants it airtight: strict-provable \(\subseteq\) permissive-provable, since permissive weakening subsumes the vacuous-discharge case that strict adds; hence permissive failure transfers to strict.)
5. What stays outside Prolog
To close without overclaiming: the one inference the model cannot make on its own is the step from “no derivation of depth \(\le 6\) was found” to “no derivation exists”. That the bound is adequate for these shallow sequents is an argument about the search space, made outside Prolog — and it is exactly the inference the three kernels replace by taking the antisequent as an audited hypothesis. That is the seam between corroboration and certification, and it is the only one. Everything on the Prolog side of it — the rules are \(\mathcal{F}\) and all of \(\mathcal{F}\); the rules prove the real theorems; the two branches of the contradiction are searched for and constructed before one’s eyes — is a finite, runnable, exhibitable fact.
6. Run it
Nothing here needs to be taken on trust: consult
core_logic_is_not_paraconsistent.pl at
SWI-Tinker (in the
browser, nothing to install) and type main., or any single command —
fragment., dns_rules., dns1_sem_inv., dns1_inv_cut.,
dns1_anti., theorem., corollary., core_strict.. All sources — the
three frozen certifications and this Prolog model — are in the
GitHub
repository; the certifications are untouched and their SHA256 hashes
unchanged.
— for Jan Wielemaker, again: the model runs where it does because SWI-Prolog runs where it does.
7. References