Terms up close: functors, atoms and sorting
Taking a structure apart with functor, arg and univ, turning a term into a list and back, and the helpers that treat atoms as text.
Every value is a term
Numbers and words are terms, and so is anything built from a name, some arguments and an arity: book(dune, 288) has the functor book with arity two. Structure is not hidden away, because a program may ask for the name, the arity and any single argument of a term it is holding.
A term is either atomic or compound. An atom, a number and a variable are atomic: they have no arguments. A compound term has a functor and at least one argument, and its arity is the number of arguments. book(dune, 288) is compound with arity two; dune is an atom and has arity zero, which is why functor/3 on an atom reports the atom itself.
functor/3 reports the name and the arity, arg/3 picks one argument by counting from one, and =../2 turns a term into a list with the functor in front. That last one is how a program inspects a structure it did not expect when it was written.
Atoms as text
Atoms are the words of the language and they come with helpers of their own. One counts the characters in an atom, another joins two atoms into a third, and a third turns an atom into a list of single-character atoms, which is how you walk a word letter by letter. The snippet and the example use them by name.
sort/2 orders a list and drops duplicates in the same pass, so it is the tidy way to answer a question like which values came up at all.
atom_concat/3 works in more than one direction. With the first two arguments bound it joins them; with the third bound and one of the others free it splits the atom, offering each possible division on backtracking. That is useful for stripping a prefix, and it is also why a call with too little bound can enumerate more answers than you expected.
Terms you build while the program runs
A term can be constructed at run time as easily as it can be inspected. T =.. [point, 3, 4] builds the same structure as T = point(3, 4), because =.. works in both directions: give it a term and it hands back a list, give it a list and it hands back a term.
That direction is what makes generic code possible. A rule can receive a list of arguments, put the functor in front, and build the term it describes; or it can take a term it has never seen, ask for its functor and arity, and walk its arguments by number. Atoms are terms too, and the text helpers treat them as sequences of characters: atom_chars/2 bridges a word and a list, and atom_concat/3 joins two atoms into a third.
=.. is the bridge between a term and its parts. book(dune, 288) =.. L binds L to [book, dune, 288], and T =.. [book, dune, 288] builds the term. The list always has the functor first, so its length is the arity plus one. That off-by-one is the usual mistake when reading the parts list.
Reading a term’s shape
Think of book(dune, 288) as a small tree: a root labelled book with two children, the atom dune and the number 288. functor/3 reads the root, arg/3 picks a child by its position, and =.. flattens the whole tree into a list. A nested term such as box(point(1, 2), point(5, 6)) is the same idea one level deeper.
The tree shape is why matching is fast when you know the pattern and why generic code is slower. A pattern such as book(Title, Pages) goes straight to the two positions, while a loop over arg/3 asks the engine for each position in turn. Use the pattern when the shape is known, and the generic tools when it is not.
Opening a term up
% Every value is a term: a functor, an arity, arguments.
main :-
T = point(3, 4),
functor(T, Name, Arity),
write(Name), write(' with '), write(Arity), nl,
arg(1, T, X),
write('first '), write(X), nl,
T =.. Parts,
write('parts '), write(Parts), nl.
:- initialization(main).A book, taken apart
Run it and match each line to the goal that printed it. Then print a different argument of the book, and a different position of the parts list.
Three mistakes to watch for. functor/3 on a number gives the number itself, not a name, so check that the term is compound before you expect arguments. arg/3 counts from one, not zero, and an index that is too large simply fails. And =../2 copies a whole structure into a list, which is fine for a small term and wasteful in a loop; match the shape with a pattern when you know it.
Try these next
- Build
book(dune, 288)from a list with=..and print its second argument. - Turn
prologinto a list of characters and print the third one. - Join
sunanddaywithatom_concat/3and print the result.
Build a term, then read it
Run it and match each line to the goal that produced it. Then build a different term from a three-item list.
A term built while the program runs is the same kind of thing as a term you typed into the file, so the same rules match it. That is what makes the next chapter possible.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does functor(book(dune, 288), Name, Arity) give you?
