
Prolog: Give me the rules and I will find an answer for you
🧠 This is part of my series inspired by Seven Languages in Seven Weeks (Tate, 2010). I’m exploring each language not to master it, but to learn how different paradigms shape how we think as software engineers. These are the takeaways I’d share with another backend dev over coffee.
🧵 Prolog in a Nutshell
Prolog is very different from your standard all-purpose language. It is indeed classified as a declarative language.
What does that even mean?
Unlike imperative languages where - let's say you want do do a cake for illustrations purpose - you describe the recipe, the ingredients, and step-by-step process to do it, with Prolog you give it the characteristics of the cake you want and about of rules of how its supposed to be and Prolog handles the rest.
Let's take an example:
countdown(0) :- !.
countdown(N) :-
N > 0,
write(N),
nl,
N1 is N - 1,
countdown(N1).
Here, you can see it’s nothing like what we’re used to. You have two rules:
- Stop the program when we reach zero
- If N > 0, write N, print a new line, create a new variable N1 (N - 1), and recurse
Pretty cool, huh?
And to run it:
eyji$ gprolog ─╯
GNU Prolog 1.5.0 (64 bits)
Compiled Jul 8 2021, 23:55:41 with /usr/bin/clang
Copyright (C) 1999-2021 Daniel Diaz
| ?- ['recursiveCounting'].
compiling /Users/eyjicuff/prolog/recursiveCounting.pl for byte code...
/Users/eyjicuff/prolog/recursiveCounting.pl compiled, 15 lines read - 1635 bytes written, 4 ms
(1 ms) yes
| ?- countdown(5)
.
5
4
3
2
1
yes
| ?-
🧠 Takeaway #1: Please don't forget the dots
Syntax in Prolog is pretty different. It answers you, so every time you ask for a solution, you’re querying the facts and world rules you’ve set to find the solution you want.
Take this set of rules:
likes(wallace, cheese).
likes(grommit, cheese).
likes(wendolene, sheep).
friend(X, Y) :- \+(X = Y), likes(X, Z), likes(Y, Z).
You basically defined a fact that wallace likes cheese. Then you created a "friends" rule saying that X can't be equal to Y (they can be the same person), and that they are friends if they like the same Z. Very cool right?
What is going on under the hood?
Prolog is using unification, a concept that lets it stretch itself to match both sides of a rule. It takes the rules and facts you gave it and tries to infer a logical solution using backtracking.
🧠 Takeaway #2: The perfect usage
Prolog is a perfect language for solving resource-constrained problems. It reminded me a lot of my Operations Research classes, where I used Lingo from LINDO Systems—a linear programming tool for optimized models. The difference? LINDO gives you an optimized solution based on variables and a goal (e.g., minimizing cost), while Prolog gives you a valid solution regardless of optimization.
Here’s a simple factory problem where you schedule two production lines that share some resources:
% Define the steps each line performs with required machines
line_a_steps([step(cutter, a), step(welder, a), step(packer, a)]).
line_b_steps([step(assembler, b), step(welder, b), step(packer, b)]).
% Available time slots
time_slot(1).
time_slot(2).
time_slot(3).
% Assign a time to each step
assign_times([], []).
assign_times([step(Machine, Line)|Rest], [step(Machine, Line, T)|TimedRest]) :-
time_slot(T),
assign_times(Rest, TimedRest).
% Ensure no two lines use the same machine at the same time
no_conflicts([]).
no_conflicts([step(Machine, Line, Time)|Rest]) :-
\+ (member(step(Machine, OtherLine, Time), Rest), Line \= OtherLine),
no_conflicts(Rest).
% Main predicate to generate a valid schedule
schedule(Schedule) :-
line_a_steps(StepsA),
line_b_steps(StepsB),
assign_times(StepsA, TimedA),
assign_times(StepsB, TimedB),
append(TimedA, TimedB, Schedule),
no_conflicts(Schedule).
% Used like
?- schedule(S).
S = [step(cutter, a, 1), step(welder, a, 2), step(packer, a, 3),
step(assembler, b, 1), step(welder, b, 3), step(packer, b, 2)].
You can see that both lines will reverse steps 2 and 3, that way both resources will be occupied at the same time! You need to know that prolog will solve problems using depth-first. If we had more valid solutions we would have to keep asking Prolog for more by using ;
.
🧭 Drawing a Line from Prolog Back to Python
Prolog and Python felt like worlds apart.
- 🧱 Python is imperative and procedural (tell the computer how to do things step by step).
- 🔍 Prolog is declarative (describe what you want, and let the engine figure out how).
- 👉 Unification is different than assignment
🧭 Final Thought
Prolog teaches you how to better describe your problem and world so you can ask it the correct questions
Ontop of that, you can do some pretty cool stuff like solving this Towers of Hannoi:
% 3 pegs, only one disk at a time, a larger disc cannot be moved on top of a smaller one
towers_of_hannoi(1, Source, Target, _) :-
write('Move disk from '), write(Source), write(' to '), write(Target), nl.
towers_of_hannoi(N, Source, Target, Aux) :-
N>1,
N1 is N-1,
towers_of_hannoi(N1, Source, Aux, Target),
towers_of_hannoi(1, Source, Target, Aux),
towers_of_hannoi(N1, Aux, Target, Source).
or this sudoku solver that can be expanded:
% Rules
% Puzzle and solution should be the same (end of the game)
% Board is a grid of 4x4
% Board has 4 rows, 4 columns, 4 squares
% The games only ends when when each row, column, and square has no repeated elements
valid([]).
valid([Head|Tail]) :-
fd_all_different(Head),
valid(Tail).
sudoku(Puzzle, Solution) :-
Solution = Puzzle,
Puzzle = [
S11, S12, S13, S14,
S21, S22, S23, S24,
S31, S32, S33, S34,
S41, S42, S43, S44
],
fd_domain(Solution, 1, 4),
Row1 = [S11, S12, S13, S14],
Row2 = [S21, S22, S23, S24],
Row3 = [S31, S32, S33, S34],
Row4 = [S41, S42, S43, S44],
Col1 = [S11, S21, S31, S41],
Col2 = [S12, S22, S32, S42],
Col3 = [S13, S23, S33, S43],
Col4 = [S14, S24, S34, S44],
Square1 = [S11, S12, S21, S22],
Square2 = [S13, S14, S23, S24],
Square3 = [S31, S32, S41, S42],
Square4 = [S33, S34, S43, S44],
valid([Row1, Row2, Row3, Row4, Col1, Col2, Col3, Col4, Square1, Square2, Square3, Square4]).
Want more like this? Subscribe here to get new posts straight to your inbox.