When the facts disagree, say so, instead of pretending they don’t.
paraconsistent-animals.js · output · proof · check · try it in the playground
The question
Birds fly. Penguins don’t. Tweety is a bird and a penguin. Does Tweety fly?
Real data is often like this: two rules, or a rule and an observation, point in opposite directions. Ordinary logic breaks down here: from a contradiction it lets you conclude anything at all.
Paraconsistent reasoning is reasoning that keeps working when the data contains contradictions. It keeps the conflict contained and visible.
What we tell eyecode: animals and rules
fact(bird('tweety'))
fact(penguin('tweety'))
fact(bird('falco'))
fact(penguin('opus'))
fact(mammal('batsy'))
fact(bat('batsy'))
fact(fish('nemo'))
fact(bird('mythic'))
// An explicit observation that contradicts the rule for birds.
fact(observed('mythic', 'flies', 'false'))
implies(bird(X), flies(X, 'true'))
implies(bird(X), wings(X, 'true'))
implies(penguin(X), flies(X, 'false'))
implies(mammal(X), wings(X, 'false'))
implies(bat(X), [flies(X, 'true'), wings(X, 'true')])
// …
Conclusions are written as flies(X, 'true') or flies(X, 'false'), so both
can be recorded side by side without the program falling over.
What we tell eyecode: summaries
Each property gets a local summary: true only, false only, or both.
// Local summaries. These inspect the rule conclusions, so they run only after
// the stratum above has reached its fixpoint.
implies([flies(X, 'true'), flies(X, 'false')], flight_status(X, 'both'))
implies([flies(X, 'true'), not(flies(X, 'false'))], flight_status(X, 'true_only'))
// …
implies(flight_status(X, 'true_only'), flies_safely(X, 'true'))
implies(flight_status(X, 'both'), flies_safely(X, 'undecided'))
not(...) means “not found”: nothing eyecode knows says otherwise. Because
the summaries look at what the animal rules concluded, eyecode runs them only
after those rules have finished (this is called stratified negation), so a
“not found” is never premature. Decisions read
only the summary, so a conflict leads to undecided, never to both answers.
What eyecode concludes
50 conclusions in all. The interesting ones:
inconsistent('tweety', 'flies')
needs_review('tweety', 'flies')
inconsistent('mythic', 'flies')
inconsistent('batsy', 'wings')
flies_safely('tweety', 'undecided')
flies_safely('falco', 'true')
flies_safely('opus', 'false')
moves_by('opus', 'walking')
Tweety (bird and penguin) and Mythic (a bird seen not flying) are flagged for review. Batsy the bat has wings by one rule and none by the mammal rule. Falco and Batsy move by flying, Opus walks, and Nemo has no wings. Nothing is concluded about properties with no evidence: no rule says whether Nemo flies, so Nemo gets no flight decision at all.
Why: the proof in plain words
For Tweety:
- Tweety flies — birds fly, because
bird('tweety'). - Tweety does not fly — penguins don’t, because
penguin('tweety'). - So Tweety’s flight status is both, and Tweety needs review.
For Falco:
- Falco flies — birds fly.
- Nothing says Falco does not fly (a search that found nothing).
- So Falco’s status is true only, and Falco flies safely.
Checked, not just claimed
The proof has 66 steps: 59 were verified against the program, and the other 7 are the “not found” steps described below. Verdict: checked_with_obligations.
The 7 obligations are all of the kind called absent, one for each “not
found” the decisions rely on, such as:
obligation('absent', 'theory_scoped', not(flies('falco', 'false')))
eyecode concluded that nothing says Falco cannot fly because it searched everything it knows and found nothing. The checker records each such absence rather than proving it, and confirmed that no step in the proof contradicts any of them.
Try it
eyecode examples/paraconsistent-animals.js # the answers
eyecode --proof examples/paraconsistent-animals.js # answers with their proof
Or open it in the playground.
Delete the line fact(observed('mythic', 'flies', 'false')) and run again: the
conflict disappears, and you get flies_safely('mythic', 'true') and
moves_by('mythic', 'flying').
Takeaway
Contradictions in data are not a reason to give up or to guess. Kept local and labelled, they become a to-do list for a human, while everything that is clear-cut still gets decided.
