A quick introduction to prolog
One thing I miss in my academic life is the constant learning of new languages and technologies, and one of the disciplines that gave more pleasure was artificial intelligence. I once had a project were we had to code some popular board games, like chess or checkers, with highly skilled virtual opponents. In my case I had to code a game called mancala, which is a popular asian/african game I think, and it was once available in nokia cell phones about 10 years ago I think. The language of choice for that assignment was prolog, and I really enjoyed learning it, and all it’s associated programming paradigms. I haven’t used prolog since that project, and I was completely unaware of how it has evolved and current applications. Apparently it never really left the academic and scientific worlds, but I can see great potential in it, specially in working with data. Prolog is considered a logical programming language, and probably the most popular in this subset of the programming languages world. It is mainly associated with artificial intelligence and computational linguistics.
So I decided to make some research and try to get back on the tracks with Prolog, mainly by pure curiosity, and decided to write this small beginners tutorial. If you want to follow these examples, you can download the SWI Interpreter here. This is important, because different implementations sometimes have incompatible semantics, although swi uses a standard, the “Edinburgh Prolog” dialect, so all the compilers / interpreters compatible with this standard should run the examples, which are fairly simple.
Prolog programs / scripts are structured in terms of relations between data. We can then build queries around those relations. Has a starting point, I’ll introduce some data to prolog:
specie(max, dog). specie(buddy, dog). specie(tigger, cat). specie(oliver, cat).
Max and buddy, but also dog and cat for example, are atoms they are just the textual representation of your data, and have no inherent meaning. Each line of this sample is known as a unit clause, and represents the relation between atoms. There’s no need to declare nothing, just start typing the data and it’s relations. Its like saying in plain English, max is a dog, buddy is a dog, tigger is a cat, oliver is a cat.
Save the file somewhere in your hard drive, save it with a .pl extension, then open the interpreter and consult it using the command ['path/to/the/file.pl']. , square brackets included, and don’t forget the final dot (.). Here is a sample of a SWI interpreted session and some queries on our pet data:
1 ?- ['c:/pl/pets.pl']. % c:/pl/pets.pl compiled 0.00 sec, 2,568 bytes true. 2 ?- specie(max, Wich). Wich = dog. 3 ?- specie(Pet, cat). Pet = tigger ; Pet = oliver.
The first query, just asks the interpreter to consult the pets.pl file, nothing special here, the second query starts showing some of the prolog magic, it aska the interpreter Which is max specie’s, and it replies saying that Wich is dog. I almost feels like we’re talking with the computer
The third query has multiple results. By default, the interpreter will just provide us the first result, but we have the option to type an extra semi collon (;) after each answer, so the interpreter will list the next possible answer, until all were already given. In this case, we are told that tigger and oliver are the pets belonging to the cats specie. Also notice that the words Which and Pet start with an upper case character. This is the way to distinguish between simple atoms and variables.
What if we want to know which pets share the same owner. If all your prolog background was acquired on the previous lines of this post, then you’re probably considering adding some more unit clauses describing the same owner relation between pets. Thats a valid solution, and not very tedious to implement if we just have a couple of pets or are not retrieving the information from a database for example. So we are building a functor. Think of it as a function (unit clausesI are also functors, but without a body):
sameOwner(A, B) :- owner(A, O1), owner(B, O2), O1 = O2, not(A = B).
The first line represents the functor head, composed by the name, followed by a list of arguments and the characters :- marking the beggining of the functor body. The body is the composed by a subset of queries separated by a comma, that must be satisfied in order to the relation described in the functor to be satisfied, ending with a dot. We can translate the functor body into plain English by something like: Load the owner of A into O1, load the owner of B into O2, check if O1 and O2 are the same and check if A and B are not the same. This last query is here to specify that one pet does not have a sameOwner relation with itself. This prevents the query number five in the next example to retrieve buddy as a valid solution.
4 ?- sameOwner(max, tigger). true. 5 ?- sameOwner(buddy, Pet). Pet = oliver.
By the way, if you’re following the examples and are using the SWI interpreter, after modifying the file just go to “File/Reload modified files” in the interpreter to re-consult the pets file.
Query number 4, looks like an unit clause, but it’s not, we are querying the interpreter if max and tigger have the same owner. Query number 5 is just asking which pets share the same owner with buddy. Now let’s assume that john and mike are married, so their wives are also owners of their pets. First of all we need to describe the marriage relations, and then we need to describe the ownership relation between a pet and a wife:
owner(Pet, Wife) :- owner(Pet, Husband), married(Husband, Wife). married(john, sarah). married(mike, mary).
As you can see, we just added a new functor, describing the ownership relation between pets and wives, and some unit clauses describing the marriages. The functor has the same name has the previous owner unit clauses, but take a look at the following queries before the explanation :
6 ?- owner(max, sarah). true . 7 ?- owner(max, Who). Who = john ; Who = sarah ;
Query number 6 asks if sarah is one of the owners of max. What prolog does basically, is to check for all the functors called owner (remember, unit clauses are also functors) until one of them returns a Yes, or until all of them have been tested and there’s no positive answer, which in that case represents a failed query, with a No as an answer. So, even after checking the clause that states that john is max owner, the interpreter will keep trying, until it reaches the functor with the pets wives relation, and because sarah is john wife, the query is satisfied. Query number 7 asks for all the max owner’s. Once again, the interpreter will check all the conditions until he gets a good answer. As soon as a positive match pops, the interpreter shows it to us, and if we type a semi collon, it will kepp searching for more matches, until all are found. Finally, take a look at this last query:
8 ?- owner(Pet, Person). Pet = max, Person = john ; Pet = buddy, Person = mike ; Pet = tigger, Person = john ; Pet = oliver, Person = mike ; Pet = max, Person = sarah ; Pet = buddy, Person = mary ; Pet = tigger, Person = sarah ; Pet = oliver, Person = mary ;
What we did here was to ask the interpreter for all matches between pets and persons based on the owner relationship. And it nicely replies to us with a last of all matches, wives included. Nice uh?
This concludes the tutorial, hope you like it, and find it useful. Depending on the feedback I get with this post, I might add some more examples in the future. I also found this prolog to sql interface, and I think I’ll give it a try latter and post the results here.
Cheers