1.1 From theory to tool
Unit 3 gave the model; this unit is hands-on Prolog (PROgramming in LOGique, Colmerauer & Roussel, 1972). Install SWI-Prolog (free, all platforms). Save programs in a .pl file, load with [myfile]. or consult(myfile)., query at the ?- prompt; ; asks for more answers, . accepts.
1.2 Term syntax — everything is a term
| Term kind | Examples | Rule | ||
|---|---|---|---|---|
| Atom | apple, ram, 'New Delhi', + | starts lowercase, or quoted | ||
| Number | 42, -3, 2.5 | integers & floats | ||
| Variable | X, Result, _Tmp, _ | starts uppercase or _ | ||
| Compound | date(12, jun, 2026), point(X, Y) | functor(args); functor is an atom | ||
| List | [1, 2, 3], `[H | T], []` | sugar for `'[ | ]'(H, T)` cells |
| String | "text" | (SWI) a string object |
The arity matters: foo/2 and foo/3 are different predicates. Operators are just compound terms in disguise: 2 + 3 is the term +(2, 3) — not the number 5!
1.3 Arithmetic: is vs =
The single most common beginner trap:
| Goal | What it does | Result |
|---|---|---|
X = 2 + 3 | unification — no evaluation | X = 2+3 (a term!) |
X is 2 + 3 | evaluate right side, unify with left | X = 5 |
5 =:= 2 + 3 | evaluate both sides, compare values | true |
5 == 2 + 3 | structural identity, no evaluation | false |
Arithmetic comparisons (< > =< >= =:= =\=) evaluate both sides; note "less or equal" is =< (not <=). Useful functions: + - * /, // (integer div), mod, abs, min, max, sqrt.
% factorial
fact(0, 1).
fact(N, F) :- N > 0, N1 is N - 1, fact(N1, F1), F is N * F1.
% fibonacci
fib(0, 0).
fib(1, 1).
fib(N, F) :- N > 1, A is N-1, B is N-2,
fib(A, FA), fib(B, FB), F is FA + FB.
% sum of a list
sumList([], 0).
sumList([H|T], S) :- sumList(T, ST), S is H + ST.
% maximum of a list
maxList([X], X).
maxList([H|T], M) :- maxList(T, MT), (H >= MT -> M = H ; M = MT).
Remember: is needs its right-hand side fully instantiated — X is Y + 1 with unbound Y is an error. Order your goals so variables are bound before arithmetic uses them.
1.4 Controlling the search: the cut (!)
The cut ! always succeeds, but as a side effect it freezes the choices made so far in the current clause: on backtracking past the cut, the whole predicate call fails — no other clauses of this predicate are tried, and no alternatives for the goals before the cut are revisited.
% classify with cuts ("if-then-else ladder")
classify(N, negative) :- N < 0, !.
classify(N, zero) :- N =:= 0, !.
classify(_, positive).
% max with cut
max(X, Y, X) :- X >= Y, !.
max(_, Y, Y).
- Green cut: removes only choices that could never succeed — pure efficiency; meaning unchanged.
- Red cut: removes real solutions — the program's logic now depends on the cut (like
max/3above: delete the cut andmax(5,3,3)succeeds wrongly). Use red cuts sparingly and document them.
Related built-ins: fail (always fails — combine with backtracking to iterate), true, the if-then-else ( Cond -> Then ; Else ), and \+ (negation as failure, from Unit 3).
% print every parent pair using the fail-driven loop idiom
printAll :- parent(X, Y), format("~w is parent of ~w~n", [X, Y]), fail.
printAll.
1.5 Input/output basics
| Built-in | Purpose |
|---|---|
write(T), writeln(T), print(T) | write a term |
nl | newline |
read(X) | read a term ending with '.' from input |
format("~w and ~w~n", [A, B]) | formatted output |
1.6 A complete worked micro-program
% Grade lookup with validation
grade(M, _) :- (M < 0 ; M > 100), !, write('invalid marks'), nl, fail.
grade(M, 'A+') :- M >= 90, !.
grade(M, 'A') :- M >= 75, !.
grade(M, 'B') :- M >= 60, !.
grade(M, 'C') :- M >= 40, !.
grade(_, 'Fail').
?- grade(82, G). % G = 'A'
?- grade(105, G). % invalid marks → fails
This combines arithmetic comparison, cut-as-else, failure for validation and I/O — the full toolkit of this lesson in ten lines.
1.7 The cut, precisely — what exactly gets discarded
Definition. When!is executed in a clauseH :- B1, ..., !, ... Bnit succeeds immediately and commits to (a) the clause currently being used for the call toH, and (b) all bindings made byB1...before the cut. On backtracking into the cut, the entire call to H fails — remaining clauses ofHare never tried and alternatives for the pre-cut goals are never revisited. Goals after the cut backtrack normally among themselves, and goals before the call toHare unaffected.
Trace showing the difference:
p(1). p(2). p(3).
q(X) :- p(X), !. % cut version
r(X) :- p(X). % no cut
?- q(X). % X = 1. — one answer; ';' finds nothing more
?- r(X). % X = 1 ; X = 2 ; X = 3.
Green cut vs red cut — the comparison table:
| Green cut | Red cut | |
|---|---|---|
| Removes | branches that would fail anyway | branches containing real solutions |
| Declarative meaning | unchanged — delete the cut, same answers | changed — delete the cut, wrong answers |
| Purpose | efficiency only | the logic depends on clause order + cut |
| Example | max(X,Y,X) :- X >= Y, !. with max(X,Y,Y) :- X < Y. | max(X,Y,X) :- X >= Y, !. with bare max(_,Y,Y). |
| Verdict | safe, encouraged | document it, or refactor to if-then-else |
Checking the red-cut example: with the bare second clause, ?- max(5, 3, M). correctly gives only M = 5 — but query ?- max(5, 3, 3). succeeds via clause 2 (the head max(_, Y, Y) unifies directly, the cut in clause 1 was never reached because the first head max(X,Y,X) requires the 1st and 3rd arguments to unify). That stray success is the hallmark of red-cut fragility — quote it when asked "what is the danger of cuts?".
1.8 cut-fail: expressing "definitely not"
The !, fail combination implements early rejection — and is exactly how \+ can be defined:
% "ground birds fly, except penguins"
flies(penguin) :- !, fail. % cut commits, fail rejects — no clause 2 rescue
flies(X) :- bird(X).
% negation as failure, defined with cut-fail (a classic theory question)
not(G) :- call(G), !, fail. % G provable → not(G) fails
not(_). % G not provable → not(G) succeeds
1.9 Tail recursion with accumulators — Prolog edition
The naive fact/2 builds up N F1 multiplications after* the recursive call returns. The accumulator version does the work on the way down, enabling last-call optimisation (constant stack):
factA(N, F) :- factAcc(N, 1, F).
factAcc(0, Acc, Acc).
factAcc(N, Acc, F) :- N > 0, Acc1 is Acc * N, N1 is N - 1, factAcc(N1, Acc1, F).
?- factA(5, F).
% factAcc(5,1,F) → factAcc(4,5,F) → factAcc(3,20,F)
% → factAcc(2,60,F) → factAcc(1,120,F) → factAcc(0,120,F) → F = 120
Compare with revAcc (Unit 3) and Haskell's revFast (Unit 2): the accumulator idiom is paradigm-independent — one of the recurring "common threads" questions.
1.10 Trace of fact(3, F) — the exam staple
fact(3, F) : 3 > 0, N1 = 2, call fact(2, F1)
fact(2, F1): 2 > 0, N1 = 1, call fact(1, F2)
fact(1, F2): 1 > 0, N1 = 0, call fact(0, F3)
fact(0, F3): matches fact(0, 1) → F3 = 1
F2 is 1 * 1 → F2 = 1
F1 is 2 * 1 → F1 = 2
F is 3 * 2 → F = 6
Show the descent (calls) and the ascent (the is evaluations) as separate phases — that structure is what earns the trace marks.
1.11 Term comparison operators — the complete reference
| Operator | Compares | Evaluates? | 2 + 3 vs 5 |
|---|---|---|---|
= | unifiability | no | succeeds only by binding — here neither is a variable, fails |
== | structural identity | no | fails (+(2,3) ≠ 5) |
\== | structural difference | no | succeeds |
=:= | numeric value | both sides | succeeds (5 = 5) |
=\= | numeric difference | both sides | fails |
is | evaluate right, unify left | right only | X is 2+3 → X = 5 |
1.12 Common Prolog errors decoded
| Symptom | Usual cause | Fix |
|---|---|---|
Arguments are not sufficiently instantiated | is/comparison on an unbound variable | reorder goals to bind first |
| Query loops forever | left recursion, or missing N > 0 guard | base clause first; guard the recursive clause |
false where you expected an answer | typo'd predicate name or wrong arity (foo/2 vs foo/3) | check with listing(foo). |
| Singleton-variable warning | variable used once (often a typo: Reslt) | rename or prefix with _ |
| Extra unwanted answers | missing cut or missing inequality (X \= Y) | add the guard, or restructure with if-then-else |
Exam pointers
- "Explain the cut with green/red examples" — the boxed definition, the
q/rtrace, and the table of §1.7 are a complete 8-mark answer. - "Differentiate
=,is,==,=:=" — reproduce §1.11; the2 + 3vs5column is the memorable part. fact,fib,sumList,maxList, GCD — the standard arithmetic predicates; write each with its guard and be ready to trace it as in §1.10.- "Implement negation using cut and fail" — §1.8's two-clause
not/1is asked surprisingly often.
Self-check
- For
p(1). p(2). p(3)., what does?- p(X), !, X > 1.answer, and why is it different from?- p(X), X > 1, !.? - Write
gcd/3usingmod, with proper guards. - Which of these succeed?
X = 1+1,2 == 1+1,2 =:= 1+1,X is 1+1, X == 2. - Convert
classify/2from §1.4 to a single clause using if-then-else( -> ; ). - Trace
?- sumList([4,5], S).showing descent and ascent phases.