Control Flow in Prolog
Learn how control flow works in Prolog through backtracking, if-then-else, cut, negation as failure, and recursion with Docker-ready examples
Control flow in Prolog looks nothing like the if/for/while machinery of imperative languages — and that is the whole point. Prolog is a logic programming language, so the engine’s default behavior is control flow. When you pose a goal, Prolog searches its knowledge base for a way to prove it, and when a choice fails it backtracks to try the next alternative. You don’t write the loop; the search strategy is the loop.
That said, Prolog gives you explicit tools to shape the search when you need them: the ( Cond -> Then ; Else ) if-then-else construct, the cut (!) to commit to a decision and prune the search, and negation as failure (\+) to ask “can this not be proven?”. Repetition that other languages express with loops is expressed in Prolog with recursion, or with all-solutions predicates like forall/2, findall/3, and between/3.
This tutorial works through each of these. By the end you’ll understand how to make decisions, iterate over ranges and solutions, commit to choices with the cut, and express loops as recursive predicates — all while thinking in terms of proof and search rather than step-by-step instructions.
Conditionals: Multiple Clauses and If-Then-Else
There are two idiomatic ways to make a decision in Prolog. The first is the most declarative: write several clauses, each with its own condition, and let unification and backtracking pick the one that applies. The second is the explicit ( Condition -> Then ; Else ) operator, which reads much like a traditional if/else chain.
Create a file named if_then_else.pl:
| |
The sign/2 predicate shows the declarative style: each clause is a separate fact-like rule, and Prolog tries them in order. The grade/2 predicate uses the if-then-else operator, where -> means “if the condition on the left succeeds, commit to the goal on the right.” Only the first matching branch runs — the -> quietly cuts away the alternatives.
Backtracking: The Loop You Didn’t Write
Backtracking is Prolog’s native control flow. A predicate with multiple matching clauses is a generator of solutions, and the engine will walk through every one of them on demand. We can harvest those solutions with findall/3 (collect them all into a list) or iterate over them with forall/2 (a side-effecting “for every solution, do this”).
Create a file named backtracking.pl:
| |
Notice there is no nested loop here. The goal meal(Color, Dish) calls color(Color) and dish(Dish); Prolog automatically tries every color paired with every dish, backtracking through all six combinations. findall/3 gathers them into the list Meals, and forall/2 prints each one. This is how Prolog expresses “for each X, for each Y” without any explicit iteration construct.
Recursion: Loops Without Loops
Prolog has no for or while. Repetition is expressed with recursion: a predicate that calls itself with a smaller problem until it reaches a base case. This is the single most important control-flow pattern in the language.
Create a file named recursion.pl:
| |
Each predicate has a base case (the terminating condition, like countdown(0) or factorial(0, 1)) and a recursive case that reduces the problem toward that base. The N > 0 guard in countdown/2 and factorial/2 is essential — it stops the recursive clause from matching once we’ve reached zero, which is how recursion stands in for a loop’s exit condition.
Cut and Negation as Failure
Sometimes you want to prune Prolog’s search. The cut (!) tells the engine “I’m committed to the choices I’ve made so far — don’t backtrack past this point.” It’s how you make a predicate deterministic. The complementary tool is negation as failure (\+ Goal), which succeeds precisely when Prolog cannot prove Goal.
Create a file named cut_negation.pl:
| |
In max/3, once X >= Y succeeds the cut commits to the first clause — without it, Prolog could backtrack into the second clause and produce a wrong second answer. With dislikes/2, \+ likes(bob, prolog) succeeds because there is no fact saying Bob likes Prolog, while \+ likes(alice, prolog) fails because that fact does exist. This is “closed-world” reasoning: anything not provable is treated as false.
Running with Docker
Run each example with the official SWI-Prolog image. The -q flag suppresses the startup banner for clean output.
| |
Expected Output
Running if_then_else.pl:
7 is positive
-2 is negative
0 is zero
Score 95: A
Score 83: B
Score 71: C
Score 50: F
Running backtracking.pl:
All colors (via forall + backtracking):
red
green
blue
All meal combinations:
red-soup
red-salad
green-soup
green-salad
blue-soup
blue-salad
Total combinations: 6
Running recursion.pl:
Countdown from 3:
3
2
1
Liftoff!
5! = 120
Sum of [10,20,30,40] = 100
Running cut_negation.pl:
max(7,3) = 7
max(2,9) = 9
alice likes prolog
bob does not like prolog
alice DOES like prolog
Key Concepts
- Backtracking is the default control flow — when a goal fails, Prolog automatically retreats to the last choice point and tries another alternative. Most “iteration” in Prolog is just the engine exploring solutions.
- Multiple clauses are conditional branches — writing several guarded clauses for a predicate (like
sign/2) is the declarative way to express alternatives, and it’s usually preferred over the if-then-else operator. ( Cond -> Then ; Else )is explicit if-then-else — the->commits to theThenbranch as soon asCondsucceeds, and you can chain conditions to build elif-style ladders.- Recursion replaces loops — every repetitive task uses a base case plus a recursive case that shrinks the problem; the guard (e.g.
N > 0) acts as the loop’s exit condition. findall/3,forall/2, andbetween/3let you collect or iterate over all solutions and numeric ranges without manual looping.- The cut (
!) prunes the search — it commits to choices already made, turning a non-deterministic predicate into a deterministic one. Use it deliberately, as it discards alternative solutions. - Negation as failure (
\+) succeeds when a goal cannot be proven, reflecting Prolog’s closed-world assumption: unknown means false.
Comments
Loading comments...
Leave a Comment