PrologEZ
Data & computation · Lesson 25 of 43

Inspecting and managing terms

Type checks, comparison, copy_term, terms as strings, univ, functor, arg, and console output.

Library predicates to deal with terms

So far unification is our only means to manage terms: it has been used to inspect terms (to process "input") and to create new terms (to produce "output"). Often additional expressiveness is needed, though somewhat extra-relational, like the cut.

The usual predicates and operators:

purposepredicates
inspect the shape of a termvar/1, nonvar/1, number/1, float/1, integer/1, atom/1, compound/1, ground/1
comparison=/2, \=/2, ==/2, \==/2, copy_term/2
terms as stringsatom_chars/2, atom_codes/2, atom_concat/3, char_code/2, number_chars/2, number_codes/2
term structure=../2, functor/3, arg/3
I/O over file and consolewrite/1, nl/0, ...

Inspecting terms

File inspecting-terms.pl. Typically used to check the correctness of inputs.

?- atom(a)
?- atom(10)
?- atom(a(1))
?- var(X)
?- var(a)
?- nonvar(X)
?- number(10)
?- float(10.1)
?- integer(10.1)
?- compound(10)
?- compound(a)
?- compound(a(10, 20))
?- ground(a(10, 20))
?- ground(a(10, X))

Term comparison

File term-comparison.pl. Typically used to handle and compare inputs more flexibly.

  • =/2 unifies; \=/2 is the non-unification test and never binds;
  • ==/2 is equality (no binding): two terms are equal if they are structurally identical, so a variable is equal only to itself; \==/2 is the inequality;
  • copy_term/2 clones a term, giving the copy fresh variables.
?- a(10, b) = a(10, b)
?- a(X, b) = a(10, b)
?- a(X, b) = a(Y, c)
?- a(10, b) \= a(10, b)
?- a(X, b) \= a(10, b)
?- a(X, b) \= a(Y, c)
?- a(10, b) == a(10, b)
?- a(X, b) == a(10, b)
?- a(X, b) == a(X, b)
?- a(10, b) \== a(10, b)
?- a(X, b) \== a(10, b)
?- a(X, b) \== a(X, b)
?- copy_term(a(10, X), Y)
?- copy_term(a(10, X), a(10, Y))
?- copy_term(a(10, X), a(10, X))
?- copy_term(a(10, X), a(11, X))

Notice that copy_term(a(10, X), Y) answers Y = a(10, _): the copy has a fresh variable, not X itself. Check it with ==:

?- copy_term(a(10, X), Y), Y == a(10, X)

Terms as strings

File terms-as-strings.pl. Typically used to manipulate numbers, atoms and strings computationally.

?- atom_chars(hello, L)
?- atom_chars(X, [h, e, l, l, o])
?- atom_codes(hello, L)
?- atom_codes(X, [95, 48, 32, 49])
?- atom('_0 1')
?- atom_concat(aa, bb, X)
?- number_chars(100, X)
?- number_codes(100, X)
?- char_code(C, 0'a)

The pair atom_chars / atom_codes converts an atom to and from a sequence of one-character atoms or of ASCII codes. Note that atom_codes(X, [95, 48, 32, 49]) gives the atom '_0 1': any text can be an atom.

Compound terms: (de)structuring

File term-structure.pl. Typically used to manipulate compound terms computationally.

?- p(10, q(20)) =.. L
?- X =.. [p, 10, q(20)]
?- functor(p(10, 20, 30), X, Y)
?- functor(T, p, 3)
?- arg(2, p(10, 20, 30), Y)
  • =.. ("univ") converts a compound term to a list (functor first, then the arguments) and back;
  • functor/3 extracts, or constructs, the functor name and the arity;
  • arg/3 gives the N-th argument of a compound term, directly.

I/O over the console

File console-io.pl. We can redirect standard input/output (it is the console by default), and there are predicates to write terms as strings (write/1, nl/0) and to read. We only see the usage for debugging by logging strings: the program below prints the partial sums while the recursion unwinds.

sum([], 0).
sum([H|T], N) :- sum(T, N2), write(N2), nl, N is H + N2.
?- write('start'), nl, sum([10, 20, 30], N)

The console shows start, then 0, 30, 50: the sums of the tails, computed from the last element backwards. Here the printed text appears above the answers.

A few examples

File few-examples.pl:

  • all(+Term, +List): are all the elements of List of the kind Term? It uses copy_term/2 to compare an element with a fresh copy of the pattern;
  • a fully relational size/2, which counts the length of a list when the list is given, and generates a list of the given length when it is not.
% all(+Term, +List): are all elements in List of kind Term?
all(_, []).
all(X, [Y | T]) :- copy_term(X, Y), all(X, T).

% fully-relational size
size(L, N) :- var(L), !, generate(L, N).
size(L, N) :- length(L, N).

generate([], 0) :- !.
generate([_|T], N) :- N2 is N-1, generate(T, N2).
?- all(p(X), [p(a), p(b), p(a)])
?- all(p(X), [p(a), q(b)])
?- size([10, 20, 30], N)
?- size(L, 3)

all works because copy_term(p(X), Y) gives a fresh p(_), which then unifies with the element, whatever its argument: p(a), p(b) and so on, while q(b) does not match the kind.

Exercise: What kind of term?

Write kind(T, K) where K is one of variable, number, atom or compound, according to the term T. Use the type-checking predicates (watch the order: a variable must be recognised before anything else, and kind must give exactly one answer).