9.3.2 The Structure of Terms

Given a complex term of which you don't know what it looks like, what kind of information would be interesting to get? Probably, what's the functor, what's the arity and what do the arguments look like. Prolog provides built-in predicates that answer these questions. The first two are answered by the predicate functor/3. Given a complex term functor/3 will tell us what the functor and the arity of this term are.

?- functor(f(a,b),F,A).
A = 2
F = f
yes
?- functor(a,F,A).
A = 0
F = a
yes
?- functor([a,b,c],X,Y).
X = '.'
Y = 2  
yes

So, we can use the predicate functor to find out the functor and the arity of a term, but we can also use it to construct terms, by specifying the second and third argument and leaving the first undetermined. The query

?- functor(T,f,8).

for example, returns the following answer:

T = f(_G286, _G287, _G288, _G289, _G290, _G291, _G292, _G293)
yes

Note, that either the first argument or the second and third argument have to be instantiated. So, Prolog would answer with an error message to the query functor(T,f,N). 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 and that is something Prolog can just not do.

In the previous section, we saw built-in predicates for testing whether something is an atom, a number, a constant, or a variable. So, to make the list complete, we were actually missing a predicate for testing whether something is a complex term. Now, we can define such a predicate by making use of the predicate functor. All we have to do is to check that the term is instantiated and that it has arguments, i.e. that its arity is greater than zero. Here is the predicate definition.

complexterm(X) :-
        nonvar(X),
        functor(X,_,A),
        A > 0.

In addition to the predicate functor there is the predicate arg/3 which tells us about 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 third useful built-in predicate for analyzing term structure is '=..'/2. It takes a complex term and returns a list that contains the functor as first element and then all the arguments. So, when asked the query '=..'(loves(vincent,mia),X) Prolog will answer X = [loves,vincent,mia]. This predicate is also called univ and can be used as an infix operator. Here are a couple of examples.

?- 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 ('=..') is always useful 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. As an example, let's define a predicate called copy_term which makes a copy of a term replacing variables that occur in the original term by new variables in the copy. The copy of dead(zed) should be dead(zed), for instance. And the copy of jeallou(marcellus,X) should be jeallous(marcellus,_G235); i.e. the variable X in the original term has been replaces by some new variable.

So, the predicate copy_term has two arguments. It takes any Prolog term in the first argument and returns a copy of this Prolog term in the second argument. In case the input argument is an atom or a number, the copying is simple: the same term should be returned.

copy_term(X,X) :- atomic(X).

In case the input term is a variable, the copy should be a new variable.

copy_term(X,_) :- var(X).

With these two clauses we have defined how to copy simple terms. What about complex terms? Well, copy_term should return a complex term with the same functor and arity and all arguments of this new complex term should be copies of the corresponding arguments in the input term. That means, we have to look at all arguments of the input term and copy them with recursive calls to copy_term. Here is the Prolog code for this third clause:

copy_term(X,Y) :-
       nonvar(X),
       functor(X,F,A),
       A > 0,
       functor(Y,F,A),
       X =.. [F|ArgsX],
       Y =.. [F|ArgsY],
       copy_terms_in_list(ArgsX,ArgsY).

copy_terms_in_list([],[]).
copy_terms_in_list([HIn|TIn],[HOut|TOut]) :-
       copy_term(HIn,Hout),
       copy_terms_in_list(TIn,TOut). 

So, we first check whether the input term is a complex term: it is not a variable and its arity is greater than 0. We then request that the copy should have the same functor and arity. Finally, we have to copy all arguments of the input term. To do so, we use univ to collect the arguments into a list and then use a simple list processing predicate copy_terms_in_list to one by one copy the elements of this list.

Here is the whole code for copy_term:

copy_term(X,_) :- var(X).
 
copy_term(X,X) :- atomic(X).
 
copy_term(X,Y) :-
       nonvar(X),
       functor(X,F,A),
       functor(Y,F,A),
       A > 0,
       X =.. [F|ArgsX],
       Y =.. [F|ArgsY],
       copy_terms_in_list(ArgsX,ArgsY).
 
 
copy_terms_in_list([],[]).
copy_terms_in_list([HIn|TIn],[HOut|TOut]) :-
       copy_term(HIn,Hout),
       copy_terms_in_list(TIn,TOut). 


Patrick Blackburn, Johan Bos and Kristina Striegnitz
Version 1.2.5 (20030212)