Prolog - Gateway to Logic Programming
In this notebook we talk about Prolog basics. We also learn how to benefit from Prolog's power using Python.
because logic was removed from the course’s syllabus and students might be unfamiliar with this topic it’s good to have a prerequisites subsection listing links to required skills and mathematical background such as logic itself, this notebook is trying to fulfill the lack of logic topics in the course.
Prolog is a logic programming language. It means that it is different in nature from the general-purpose form of languages like C++, Java, Python, Javascript, etc.
Prolog is based on First-Order Logic and is purely designed as a declarative programming language. The logic is expressed in terms of relations, and all the computation is initiated by queries.
The language is used for theorem proving, designing expert systems, term rewriting, automated planning, NLP, and many more interesting fields.
Do I get a job by solely learning Prolog? The answer is probably not. Prolog is not a general-purpose language and is currently used only in specific fields.
Does learning Prolog change your overall programming skill? Absolutely Yes. It uses a different paradigm than famous languages and teaches you how to tackle problems differently.
Does learning prolog help you get a better position in a company? That depends on the type of company. Suppose the company is related to Artificial Intelligence. In that case, your way of logical thinking that you get by learning Prolog and even the Prolog can help you be promoted to a better position.
For other reasons why to learn Prolog, read this article.
As a summary, I selected this sentence from the aforementioned article:
"As a student of computer science [engineering], one can make a decent career by always following the hype, but to stand out, one has to diverge from the well-trodden paths. Those willing to explore unpopular territory have a chance of being ahead of the crowd."
Actually, Prolog is a standard, and there are different implementations of it. Arguably, the most famous and widely used one is SWI-Prolog. You can download the SWI-Prolog installer from its site. SWI-Prolog Download
If you use Windows, don't forget to check the 'add swipl to the system PATH for all users' option during the installation. If you forget to do so, you can manually add C:\Program Files\swipl\bin
to the system path, given that you used the default installation path.
There are some jupyter notebook kernels available, but they mostly work on particular OS builds. Therefore, we use a python package named pyswip that runs code using SWI-Prolog. You can see the full installation guide here, but a simple pip install should suffice.
!pip install pyswip
from pyswip import Prolog, registerForeign
prolog = Prolog()
Like all programming language tutorials, the first thing is "Hello World"!
write('Hello World'),nl,write('Let\'s Learn Prolog!').
Note that all prolog statements must end with dot .
.
As we use pyswip here, we won't need Prolog write
, because we can use python print
and even use some python functions.
rainy.
father(michael , jane).
These two statements are both facts. father
is a relationship. rainy
,michael
, and jane
are atoms (constants). All atoms must start with lowercase letters unless they are enclosed in single quotes '
.
Rules: rules allow us to make conditional statements. They are defined using :-
symbol. left :- right1,right2
says IF right1
and right2
is true, then left is also true. Note that ,
acts as and operator here. Also ;
acts as or operator.
Variables: A Variable is an object we can't name at the time of execution. Variables are uppercase.
Query: In making a query, you ask Prolog whether it can prove that your statement is true. In Prolog, a query is made with ?-
symbol, but in pyswip
we can use query(string)
.
Let's see a simple example:
prolog.assertz("father(michael,jane)")
prolog.assertz("child(X,Y) :- father(Y,X)")
for solns in prolog.query("child(X , Y)"): #X and Y are prolog variables. This two lines simply print all child relationships.
print(solns["X"] , "is child of" , solns["Y"])
First line is a fact that says Michael is father of Jane. Second line is a rule that says if Y is a father of X then X is a child of Y. Note that X and Y are variables.
In the for
, we query for all child
relationships and print
them. Note that the result of query()
is a python generator and must be used in a foreach statement or casted into a list
.
consult
: You can load a database of prolog statements using consult
. It works both in prolog and pyswip. In prolog you can use both ?- consult('file.pl').
and ['file1.pl'].
. In pyswip you can use consult(path)
function.
Let's make a more complicated example and put it in a file named 1.pl
.
% 1.pl
father(albert, bob).
father(albert, betsy).
father(albert, bill).
mother(alice, bob).
mother(alice, betsy).
mother(alice, bill).
father(bob, carl).
father(bob, charlie).
parent(X,Y) :- father(X,Y); mother(X,Y).
grandparent(X,Y) :- parent(X,Z),parent(Z,Y). %X is grandparent of Y if X is parent of Z and Z is parent of Y
grandchild(X,Y) :- parent(Z,X) , parent(Y,Z). %X is grandchild of Y if Y is parent of Z and Z is parent of X
prolog.consult("prolog_files/1.pl")
for solns in prolog.query("grandparent(albert , X)"):
print("Albert" , "is grandparent of" , solns["X"])
for solns in prolog.query("grandchild(carl , X)"):
print("Albert" , "is grandchild of" , solns["X"])
Tower of Hanoi is a very famous programming problem that is used to teach recursive functions. You can see the problem definition on Wikipedia. Here we try to implement it in Prolog and solve a simple instance of it using pyswip.
% hanoi.pl
hanoi(N) :- move(N, left, right, center).
move(0, _, _, _) :- !.
move(N, A, B, C) :-
M is N-1,
move(M, A, C, B),
notify([A,B]),
move(M, C, B, A).
hanoi(N)
will lead to calling move(N)
. move (0,_,_,_)
is the termination condition of recursive call, and !
tells prolog to stop calling the function and backtracking. _
matches (unifies) with everything; It is similar to "don't care" in logic circuits. move(N,A,B,C)
is the actual recursive formulation of Tower of Hanoi. is
operator is the same as =
in other languages and here it means "put N-1
into M
". notify
is a function that will be defined in python and we will use pyswip to bind prolog and python code together.
prolog.consult("prolog_files/hanoi.pl")
N= 3
def notify(t):
print("move disk from %s pole to %s pole." % tuple(t))
notify.arity = 1
registerForeign(notify)
list(prolog.query("hanoi(%d)" % N));
registerForeign
is used to make prolog run python's notify
function. Also note that as we said before, prolog.query()
makes a generator. Generators are not evaluated by default. They must be used in a foreach or casted into list to force python actually evaluate them.
In a country, there are coins of 1,2,5,50 and 100. We want to find all the ways to sum at most max_count
of these coins to get to a cumulative sum of total
. We solve this in coins.pl
file.
:- use_module(library('bounds')).
coins(S, Count, Total) :-
% A=1, B=5, C=10, D=50, E=100
S = [A, B, C, D, E],
Av is 1,
Bv is 5,
Cv is 10,
Dv is 50,
Ev is 100,
Aup is Total // Av, % // means integer division
Bup is Total // Bv,
Cup is Total // Cv,
Dup is Total // Dv,
Eup is Total // Ev,
A in 0..Aup,
B in 0..Bup,
C in 0..Cup,
D in 0..Dup,
E in 0..Eup,
VA #= A*Av,
VB #= B*Bv,
VC #= C*Cv,
VD #= D*Dv,
VE #= E*Ev,
sum(S, #=, Count),
VA + VB + VC + VD + VE #= Total,
label(S).
bound
library is used to make the search space limited. For example if sum is 1000
, we could have at most 100
coins of value 10
. So C
should be between 0
to 100
. #=
is same as =
for algebraic expressions. sum (S,#=,Count)
means that sum of all elements in list S
should be equal to Count
. Elements of S
indicate count of each coin.
prolog.consult("prolog_files/coins.pl")
max_count = 100
total = 500
for i, soln in enumerate(prolog.query("coins(S, %d, %d)." % (max_count,total))):
S = zip(soln["S"], [1, 5, 10, 50, 100])
print(i, end=" ")
for c, v in S:
print("%dx%d" % (c,v), end=" ")
print()
N-Queens is a famous problem that asks how to put N queens in an $N \times N$ checkboard such that none of them threatens each other. As we know, in each column, there must be one queen, so we encode the state as an N-tuple in which element at index $i$ indicates which row the queen at column $i$ resides in. We can simply model this problem in Prolog like this:
:- use_module(library(clpfd)).
n_queens(N, Qs) :-
length(Qs, N),
Qs ins 1..N, %ins is in clpfd library. It check that "each" element of Qs is in range 1 to N
safe_queens(Qs),
label(Qs).
safe_queens([]). %Empty list is a good combination.
safe_queens([Q|Qs]) :- safe_queens(Qs, Q, 1), safe_queens(Qs).
safe_queens([], _, _). %Empty list is a good combination.
safe_queens([Q|Qs], Q0, D0) :-
Q0 #\= Q,
abs(Q0 - Q) #\= D0, %Diagonal Check
D1 #= D0 + 1,
safe_queens(Qs, Q0, D1).
prolog.consult("prolog_files/queens.pl")
for i,soln in enumerate(prolog.query("n_queens(%d, Qs)." % (8))):
print(i+1,".\t",soln["Qs"])
As you can see, it has found all 92 unique solutions of 8-Queen. You can test it with other numbers too. The code we wrote for this problem is much less than its implementation in most general purpose langauges and yet, it is very elegant and can be understood with little to no effort if you are familiar with prolog syntax.