Learning Pilog -3: Unification and Proof Search
5 min read
In the last post, we have seen how we can create a knowledge base and run queries on it. Today we will try to understand how Prolog computes the result of a query.
This chapter is based on this Prolog tutorial.
What is Unification?
Let's check the definition for unification:
Two terms unify if they are the same term or if they contain variables that can be uniformly instantiated with terms in such a way that the resulting terms are equal.
Sounds complicated, let's check it step by step. "Two terms unify if they are the same term". That is actually trivial. We can test that with the built-in predicate
/2 means that the predicate takes two arguments):
: (? (equal John John)) -> T : (? (equal John Susie)) -> NIL
Let's define a knowledge base as follows:
(be house_elf (Dobby)) (be witch (Hermione)) (be witch (Rita_Skeeter)) (be wizard (Harry)) (be magic () (or ((house_elf )) ((wizard )) ((witch )) ) )
We open the knowledge base with
pil magic.l + in the REPL. Now we can write a query for
magic. The defined rule says that magic is true if
@X is either a
wizard or a
: (? (magic)) =Dobby =Harry =Hermione =Rita_Skeeter -> NIL
From the output above we can see that
@X unifies with with
Rita_skeeter. To understand what is happening, let's use some additional tracing features of pilog.
First of all, we can check the defined rules with the
: (rules 'magic) 1 (be magic (@X) (or ((house_elf @X)) ((wizard @X)) ((witch @X)))) -> magic
We see that in this case there is only one rule for magic, with three subclauses which are connected by
or. Now let's trace it using
(? <predicate(s)> ( <predicate> <arg1> ...)):
: (? magic house_elf wizard witch (magic 1 (magic ) 1 (house_elf Dobby) =Dobby 1 (wizard Harry) =Harry 1 (witch Hermione) =Hermione 2 (witch Rita_Skeeter) =Rita_Skeeter))
With this we are tracing the predicates
witch. We see that
magic only appears at the very beginning, after that the other predicates are executed. Pilog tries to unify
@Xwith the first known fact which is defined in the
house_elf predicate, which returns
Dobby can be unified with
After that, Pilog moves on to the next predicate,
wizard, and then
What is happening here? Pilog is doing a backtracking tree search.
This is an important concept which we will further explore in the next post when we will talk about recursion.
Example: Crossword Puzzle
Now let's illustrate the unification process at a little more interesting example: A simple crossword puzzle for six italien words: astante, astoria, baratto, cobalto, pistola, statale. They should be arranged in the following grid:
How can we solve this with help of Pilog?
First, let's define the knowledge base. We know that we have six words with seven letters each. Let's define it like this: first comes the full word, after that comes each letter as single argument.
(be word (astante a s t a n t e)) (be word (astoria a s t o r i a)) (be word (baratto b a r a t t o)) (be word (cobalto c o b a l t o)) (be word (pistola p i s t o l a)) (be word (statale s t a t a l e))
Now we can define the rule for our crossword. Let's start with the left half of the clause: we want a valid crossword solution with six words
(be crossword ()
Basically we have only one rule: The crossword solution is valid if the letters at the intersections are identical. If you check the grid, you will agree that this means that the 2nd, 4th and 6th letter of each vertical and horizontal word must match. On the other hand, we don't care about the 1st, 3rd, 5th and 7th letter. The "don't care"-letters can be written as
@ - a so-called anonymous variable.
(be crossword () (word @ @ @ @) (word @ @ @ @) (word @ @ @ @) (word @ @ @ @) (word @ @ @ @) (word @ @ @ @) )
@H12V12 is the second letter of the first horizontal and first vertical word,
@H14V22 is the fourth letter of the first horizontal and the second letter of the second vertical word, and so on.
Now we can feed this to Pilog and query the solution. We get two valid results:
: (? (crossword)) =astante =baratto =statale =astante =baratto =statale =astoria =baratto =statale =astante =cobalto =pistola -> NIL
I think this example illustrates the power of Pilog in a very nice way: You just need to feed in the facts and the desired target, and you can get all solutions instantely without caring too much about what is happening internally.
You can find the knowledge base
In the next post we will go deeper into recursion, one of the most important concepts in Pilog/Prolog.