In the previous five modules, almost all information computed by our programs has been given to the user by using one or more variables in the query. For instance, we used the example of our_append/3 which could give the following response:
| ?- our_append([1,2], [a,b], List3).
List3=[1,2,a,b] ? ;
no
We will usually require the capability to output displays of information while our programs are running (rather than waiting until they terminate) and our displays should be well-formatted and presented.
small_display_example1(Variable) :-
% display an atom made up of several words with
% single quotes around them
write('This is a small example'),
% Variable
write('This is the variable: '),
write(Variable),
% atom (very simple)
write('And now for an atom: '),
write(atom),
% number
write('Numbers can be treated the same way: '),
write(123),
% a line to show we've finished
write('----------------------------------------').
If we now run this program using the query:
| ?- small_display_example1(variable).
This is a small exampleThis is the variable: variableAnd now for
an atom: atomNumbers can be treated the same way:123------------
----------------------------
Clearly all the text we specified has been output, but it can hardly be said to be well-formatted and presented. This is because write/1 outputs the text but doesn't move onto the next line. This is useful if we need to output text in columns (eg "And now for an atom: atom"). If we do want to move the output onto the next line we need the predicate nl/0.
small_display_example2(Variable) :-
% display an atom made up of several words with
% single quotes around them
write('This is a small example'),
nl,
% Variable
write('This is the variable: '),
write(Variable),
nl,
% atom (very simple)
write('And now for an atom: '),
write(atom),
nl,
% number
write('Numbers can be treated the same way: '),
write(123),
nl,
% a line to show we've finished
write('----------------------------------------'),
nl.
Using a similar query, we see that the layout is much improved.
| ?- small_display_example2(variable).
This is a small example
This is the variable: variable
And now for an atom: atom
Numbers can be treated the same way: 123
----------------------------------------
yes
writeln(Argument) :-
write(Argument),
nl.
We can now use this in a new definition of our example program:
small_display_example3(Variable) :-
% display an atom made up of several words with
% single quotes around them
writeln('This is a small example'),
% Variable
write('This is the variable: '),
writeln(Variable),
% atom (very simple)
write('And now for an atom: '),
writeln(atom),
% number
write('Numbers can be treated the same way: '),
writeln(123),
% a line to show we've finished
writeln('----------------------------------------').
Given a similar query to the previous ones, Prolog will return an identical display to the previous example.
The definition of writeln/1 given above is one that is likely to be of use in many programs. A usual technique is to collect such definitions into a single file which contains your definitions of utility procedures (eg our_memb/2, our_append/3, writeln/1). You can then load this program whenever you wish in the same way that you consult any other program. You can arrange for your programs to load a file automatically. For instance, suppose you have a file in the top-level of your user area and assuming your username is jmh and you use the standard prolog.ini initialisation file, you could include as the first line in your program:
:- consult('~jmh/utilities.pl').
This will be executed automatically whenever the program is loaded.
write(' '),
will output five spaces. This is cumbersome and we can use the built-in predicate tab/1 to achieve the same without the need to count the number of times we press the space bar. The following will have exactly the same effect:
tab(5),
The argument to tab/1 can be zero or any integer greater than zero. If the number is less than zero it will fail.
tab/1 becomes useful when the argument is a variable. Each time the predicate is executed it may have a different value, allowing differing displays. This is particular useful if you wish to format your output in columns.
| 22 |
| 333 |
What must our program do for each line? We can write down the steps one-by-one:
The input to this part of the program is the number (eg 22). The total number of characters from the start of the number to the second '|' is seven.
With our present knowledge we can sketch out a basic procedure to output text and we will use this as the skeleton to present our later refinements.
display_column1(Number) :-
% output '| ' - steps (i) and (ii)
write('| '),
% output number - step (iii)
write(Number),
% output spaces - not quite step (iv)
tab(5),
% output '|' and move to next line
writeln('|').
We have not managed to vary the number of spaces after the number (4th step). To do this we must find out how many characters there are in the number. Unfortunately there is no way of directly finding out the number of characters in an atom. However we do have a way of turning an atom into a list and we can find the length of a list.
| ?- name(22, List).
This will return the number in a list of two elements. But the list will not look like [2,2]:
| ?- name(22, List).
List=[50,50] ? ;
no
Where has the number 50 come from? Prolog represents some characters internally by their ASCII code and this is one of the places where it does so. For our purposes it doesn't matter what the representation is, for we only want to know how many members the list has.
| ?- name(Atom, [50,51,50]).
Atom=232 ? ;
no
This is useful if we have a list of characters that Prolog insists on viewing as ASCII characters and we wish to view in a "normal" way.
| ?- name(33, [51,51]).
yes
As an aside, suppose you have an atom which you want to divide into a list of single characters, but wish these characters to be in "alphabetical" characters rather than ASCII codes. We can write a procedure that recurses over the list of numbers, converting each one into an atom, and returning the result.
atom_to_list_of_atoms(Atom, List_of_Atoms) :-
name(Atom, List),
ascii_to_atom(List, List_of_Atoms).
% 1 boundary condition
ascii_to_atom([], []).
% 2
ascii_to_atom([ASCII|ASCII_Tail], [Atom|Atom_Tail]) :-
% ASCII is a number, and must be in the form of a list
name(Atom, [ASCII]),
ascii_to_atom(ASCII_Tail, Atom_Tail).
We can try a query such as:
| ?- atom_to_list_of_atoms(abs, Result).
Result=[a,b,s] ? ;
no
This output is at least readable.
length(List, Integer).
where integer is, of course, the length of the list. It is necessary for the first argument to be instantiated to a list, otherwise the predicate will fail. The second argument need not be instantiated.
The following query brings us closer to our objective of formatting the columns properly:
| ?- name(22, List), length(List, Len).
Len=2 ? ;
no
...
name(Number, Numb_List),
length(Numb_List, Numb_Len),
Tab_Width is 7 - Numb_Len,
tab(Tab_Width),
...
While this will work, it is not altogether good programming. The deficiency stems from the inclusion of the number '7' in the middle of the program. To someone reading the program at a later stage, its significance might not be obvious, but more importantly, being tucked away in the middle of a procedure makes it more difficult to "maintain" (ie correct or change) the program. We need to make constant values like this readily available, and the usual technique is to include this information as a fact near the beginning of the program. So more complete version of our program might be:
% *************** constants ***************
% column width
column_width(7).
display_column2(Number) :-
% output '| ' - steps (i) and (ii)
write('| '),
% output number - step (iii)
write(Number),
% output spaces - step (iv)
name(Number, Numb_List),
length(Numb_List, Numb_Len),
column_width(Col_Width),
Tab_Width is Col_Width - Numb_Len,
tab(Tab_Width),
% output '|' and move to next line
writeln('|').
We can test this procedure with a query like:
| ?- display_column(22),display_column(333).
| 22 |
| 333 |
These Pages are maintained by
Dr Peter Hancox
Last updated October 1998