9.3 Examining Terms
In this section, we will learn about some built-in predicates that let us examine terms more closely. First, we will look at predicates that test whether their arguments are terms of a certain type (for example, whether they are atoms or numbers). Then we will introduce predicates that tell us something about the internal structure of complex terms.
Types of Terms
Remember what we said about Prolog terms in Chapter 1 : there are four different kinds, namely variables, atoms, numbers and complex terms. Furthermore, atoms and numbers are grouped together under the name constants, and constants and variables constitute the simple terms. The following tree diagram summarises this:
terms simple terms variables constants atoms numbers complex terms
Sometimes it is useful to be able to determine what type a given term is. You might, for example, want to write a predicate that has to deal with different kinds of terms, but has to treat them in different ways. Prolog provides several built-in predicates that test whether a given term is of a certain type:
Let’s see how they behave.
?- atom(a). yes ?- atom(7). no ?- atom(loves(vincent,mia)). no
These three examples behave exactly as we would expect. But what happens, when we call atom/1 with a variable as argument?
?- atom(X).
no
This makes sense, since an uninstantiated variable is not an atom. However if we instantiate X with an atom first and then ask atom(X) , Prolog answers yes.
?- X = a, atom(X).
X = a
yes
But it is important that the instantiation is done before the test:
?- atom(X), X = a.
no
The predicates integer/1 and float/1 behave analogously. Try some examples.
The predicates number/1 and atomic/1 behave disjunctively. First, number/1 tests whether a given term is either an integer or a float: that is, it will evaluate to true whenever either integer/1 or float/1 evaluate to true and it fails when both of them fail. As for atomic/1 , this tests whether a given term is a constant, that is, whether it is either an atom or a number. So atomic/1 will evaluate to true whenever either atom/1 or number/1 evaluate to true and it fails when both fail.
?- atomic(mia). yes ?- atomic(8). yes ?- atomic(3.25). yes ?- atomic(loves(vincent,mia)). no ?- atomic(X) no
What about variables? First there is the var/1 predicate. This tests whether the argument is an uninstantiated variable:
?- var(X) yes ?- var(mia). no ?- var(8). no ?- var(3.25). no ?- var(loves(vincent,mia)). no
Then there is the nonvar/1 predicate. This succeeds precisely when var/1 fails; that is, it tests whether its argument is not an uninstantiated variable:
?- nonvar(X) no ?- nonvar(mia). yes ?- nonvar(8). yes ?- nonvar(3.25). yes ?- nonvar(loves(vincent,mia)). yes
Note that a complex term which contains uninstantiated variables is not itself an uninstantiated variable (it is a complex term). Therefore we have:
?- var(loves(_,mia)). no ?- nonvar(loves(_,mia)). yes
And when the variable X gets instantiated var(X) and nonvar(X) behave differently depending on whether they are called before or after the instantiation:
?- X = a, var(X). no ?- X = a, nonvar(X). X = a yes ?- var(X), X = a. X = a yes ?- nonvar(X), X = a. no
The Structure of Terms
Given a complex term of unknown structure (perhaps a complex term returned as the output of some predicate), what kind of information might we want to extract from it? The obvious response is: its functor, its arity, and what its arguments look like. Prolog provides built-in predicates that provide this information. Information about the functor and arity is supplied by the predicate functor/3 . Given a complex term, functor/3 will tell us what its functor and arity are:
?- functor(f(a,b),F,A). A = 2 F = f yes ?- functor([a,b,c],X,Y). X = '.' Y = 2 yes
Note that when asked about a list, Prolog returns the functor ., which is the functor it uses in its internal representation of lists.
What happens when we use functor/3 with constants? Let’s try:
?- functor(mia,F,A). A = 0 F = mia yes ?- functor(8,F,A). A = 0 F = 8 yes ?- functor(3.25,F,A). A = 0 F = 3.25 yes
So we can use the predicate functor/3 to find out the functor and the arity of a term, and this usage also works for the special case of 0 arity terms (constants).
We can also use functor/3 to construct terms. How? By specifying the second and third argument and leaving the first undetermined. The query
?- functor(T,f,7).
for example, returns the following answer:
T = f(_G286, _G287, _G288, _G289, _G290, _G291, _G292) yes
Note that either the first argument or the second and third argument have to be instantiated. For example, Prolog would answer with an error message to the query functor(T,f,N) . And if you think about what the query means, Prolog is reacting in a sensible way. The query is asking Prolog to construct a complex term without telling it how many arguments to provide, which is not a very sensible request.
Now that we know about functor/3 , let’s put it to work. In the previous section, we discussed the built-in predicates that tested whether their argument was an atom, a number, a constant, or a variable. But there was no predicate that tested whether its argument was a complex term. To make the list complete, let’s define such a predicate. It is easy to do so using functor/3 . All we have to do is to check that there is a suitable functor, and that the input has arguments (that is, that its arity is greater than zero). Here is the definition:
complexterm(X):- nonvar(X), functor(X,_,A), A > 0.
So much for functors — what about arguments? In addition to the predicate functor/3 , Prolog supplies us with the predicate arg/3 which tells us about the arguments of complex terms. It takes a number N and a complex term T and returns the Nth argument of T in its third argument. It can be used to access the value of an argument
?- arg(2,loves(vincent,mia),X).
X = mia
yes
or to instantiate an argument
?- arg(2,loves(vincent,X),mia).
X = mia
yes
Trying to access an argument which doesn’t exist, of course, fails:
?- arg(2,happy(yolanda),X).
no
The predicates functor/3 and arg/3 allow us to access all the basic information we need to know about complex terms. However Prolog also supplies a third built-in predicate for analysing term structure, namely ’=..’/2 . This takes a complex term and returns a list that has the functor as its head, and then all the arguments, in order, as the elements of the tail. So to the query
?- '=..'(loves(vincent,mia),X)
Prolog will respond
X = [loves,vincent,mia]
This predicate (which is called univ) can also be used as an infix operator. Here are some examples showing various ways of using this (very useful) tool:
?- cause(vincent,dead(zed)) =.. X. X = [cause, vincent, dead(zed)] yes ?- X =.. [a,b(c),d]. X = a(b(c), d) yes ?- footmassage(Y,mia) =.. X. Y = _G303 X = [footmassage, _G303, mia] yes
Univ really comes into its own when something has to be done to all arguments of a complex term. Since it returns the arguments as a list, normal list processing strategies can be used to traverse the arguments.
Strings
Strings are represented in Prolog by a list of character (ASCII) codes. However, it would be a right kerfuffle to use list notation for simple string manipulation, so Prolog also offers a user-friendly notation for strings: double quotes. Try the following query:
?- S = "Vicky".
S = [86, 105, 99, 107, 121]
yes
Here the variable S unifies with the string "Vicky" , which is a list containing of five numbers, each of them corresponding to the character codes of the single characters the strings is composed of. (For instance, 86 is the character code for the character V, 105 is the code for the character i, and so on.)
In other words, strings in Prolog are actually lists of numbers. Several standard predicates are supported by most Prolog dialects to work with strings. A particularly useful one is atom _codes/2 . This predicate converts an atom into a string. The following examples illustrate what atom _codes/2 can do for you:
?- atom_codes(vicky,X). X = [118, 105, 99, 107, 121] yes ?- atom_codes('Vicky',X). X = [86, 105, 99, 107, 121] yes ?- atom_codes('Vicky Pollard',X). X = [86, 105, 99, 107, 121, 32, 80, 111, 108|...] yes
It also works the other way around: atom _codes/2 can also be used to generate atoms from strings. Suppose you want to duplicate an atom abc into the atom abcabc. This is how you could do it:
?- atom_codes(abc,X), append(X,X,L), atom_codes(N,L).
X = [97, 98, 99]
L = [97, 98, 99, 97, 98, 99]
N = abcabc
One last thing you need to know about the atom _codes/2 predicate is that it is related to another other built-in predicate, namely number _codes/2 . This predicate behaves in a similar way, but, as the names suggest, only works for numbers.