Negation as Failure

Course 2 · Ch 3
Negation as Failure
\+ and the closed-world assumption — a real, well-documented limitation

prolog1-8 ended with a brief preview: Course 2 would properly explain \+, Prolog's negation operator, commonly built internally from exactly the cut-based pattern that chapter showed. This chapter delivers on that promise — and, in keeping with this course's own honesty about assert/retract last chapter, is equally direct about what \+ genuinely does not guarantee.

What \+ Actually Means

\+ Goal :- Goal, !, fail. \+ Goal.

Read this the way prolog1-8 taught you to read cut. If Goal succeeds, the first clause's body runs: the cut commits, then fail forces the whole thing to fail — so \+ Goal fails whenever Goal itself succeeds. If Goal fails, the first clause never gets past its own body, so Prolog falls through to the second clause, \+ Goal., which unconditionally succeeds. The net effect: \+ Goal succeeds exactly when Goal cannot be proven, and fails exactly when it can.

bird(tweety). bird(polly). bird(pingu). penguin(pingu). can_fly(X) :- bird(X), \+ penguin(X). ?- can_fly(tweety). true. ?- can_fly(pingu). false.

This is the classic, genuinely useful shape: "assume something is true unless it can specifically be shown otherwise." can_fly(pingu) fails because penguin(pingu) succeeds, so \+ penguin(pingu) fails — the negation working exactly as intended here.

The Closed-World Assumption

Notice what \+ Goal is actually testing: not "Goal is false," but "Goal cannot currently be proven from what's in the database." Prolog operates under the closed-world assumption — anything not provable from the known facts and rules is treated as false, full stop, with no third option for "unknown." That's a genuinely different, stronger claim than classical logical negation makes, and it's the real, well-documented limitation this chapter is named for.

flight(paris, london). flight(london, paris). flight(paris, rome). ?- \+ flight(paris, tokyo). true.

This succeeds — but only because this particular flight/2 database happens not to list a Paris-Tokyo route, not because a Paris-Tokyo flight is provably impossible. Absence of evidence isn't evidence of absence: \+ can only ever report "not provable from what I currently know," and an incomplete database will report that honestly, misleadingly, exactly like a complete one would.

A Concrete Gotcha — Unbound Variables Inside a Negated Goal

?- \+ flight(paris, X). false. ?- \+ flight(rome, X). true.

The first query fails, because flight(paris, X) genuinely does succeed — for X = london, among others — so its negation fails. The second succeeds, because nothing matches flight(rome, X) at all in this database. The gotcha: with a free variable inside Goal, \+ Goal is really asking "does no value of X make this true," existentially quantified over every possible binding — not "is this specific unknown X not a flight destination." And whichever way it resolves, X itself comes back unbound either way — \+ never exposes any binding Goal might have found internally, even when it fails because of one.

Claim being madeHandles an incomplete database?
Classical logical negationGoal is actually falseyes — true/false/unknown are distinct
Prolog's \+ (negation as failure)Goal cannot currently be provenno — "not provable" and "false" are collapsed into one
Ground your goal before negating it when possible
\+ behaves most predictably when every variable inside Goal is already bound (ground) by the time it runs — the "generate, then test" idiom: find a candidate value first with an ordinary goal, and only then check \+ some_condition(Candidate) against it, rather than leaving free variables for \+ itself to reason about existentially.
An incomplete database can make \+ report a confident, wrong-feeling "true"
Every use of \+ is only ever as trustworthy as the completeness of the facts and rules it's checking against. A genuinely missing flight route, a genuinely-not-yet-recorded fact, and a genuinely impossible one all look identical to \+ — this is the real, unavoidable cost of the closed-world assumption, not a bug to be fixed.

Coding Challenges

Challenge 1

Write the bird/1, penguin/1, and can_fly/1 predicates exactly as shown in the chapter, add a new fact bird(ostrich) and penguin-style fact flightless(ostrich), update can_fly/1 to also check \+ flightless(X), and confirm can_fly(ostrich) correctly fails.

📄 View solution
Challenge 2

Using the flight/2 facts from the chapter, write a query using \+ that appears to confirm there is no flight from london to tokyo, then write a comment explaining why this result does not actually prove such a flight is impossible in reality.

📄 View solution
Challenge 3

Write a short comment explaining, using \+ flight(paris, X) as the concrete example, why X remains unbound after the query runs regardless of whether the query itself succeeds or fails.

📄 View solution

Chapter 3 Quick Reference

  • \+ Goal succeeds when Goal cannot be proven, fails when Goal succeeds — built internally from Goal, !, fail exactly as prolog1-8 previewed
  • This is negation as failure, not classical logical negation — "not provable" is treated as "false," with no separate "unknown"
  • The closed-world assumption: anything absent from the database is treated as false, even if it's simply missing rather than genuinely impossible
  • \+ Goal with a free variable asks "does no value make Goal true" — existentially quantified, not testing one specific unknown value
  • \+ never exposes any variable bindings from Goal, whether it succeeds or fails
  • Ground a goal (bind its variables first) before negating it when possible, for predictable results