Some output predicates

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.

write/1

We may wish to display some Prolog term on our display medium (which we shall assume to be a window on a screen). We can use write/1 to output a variety of texts, as this following program illustrates:

     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.

nl/0

This predicate simply moves the output onto the next line. To achieve a more elegant layout, we need only a few additions:

     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/1

Some programming languages include a single statement that has the dual effect of write/1 followed immediately by nl/0. Prolog generally doesn't include this (although it is sometimes included as an added extra in some Prolog implementations). We can define our own version as a rule:

     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.

tab/1

We can output a certain number of spaces by writing the required number between single quotes to form an atom. For instance:

          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.

Formatting text

We may wish to align text in columns. This is easy if the text is of a uniform length, but harder to achieve if it varies in length. As an example, suppose we have a series of integers to output and that these numbers may be either two or three digits. An example might be:

     | 22     |
     | 333    |

What must our program do for each line? We can write down the steps one-by-one:

  1. Output '|'
  2. Output a single space
  3. Output the number
  4. Output either four or five spaces (depending on how long the number is)
  5. Output '|'
  6. Move to next line

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/2

This predicate works in three ways.

Pattern 1 - known atom, unknown list
Consider the query:

     | ?- 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.

Pattern 2 - unknown atom, known list
Following from the previous example, we can have a query that turns a list of ASCII codes into an atom:

     | ?- 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.

Pattern 3 - known atom, known list
It is of course possible to use name/2 to check the validity of some information. For instance you could check that the list [51,51] can map onto the atom '33':

     | ?- 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/2

We now have the ability to turn an atom into a list. The next step is to find the length of the list. It has the form:

     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

Formatting text revisited

We are now able to find the number of characters in an atom and we can go on to calculate the number of space needed before the following '|'.

     ...
     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    |

Take time to work through Self-Test 1.

--------------------------------------------------------------------------

Navigation Menu

--------------------------------------------------------------------------

These Pages are maintained by Dr Peter Hancox

Last updated October 1998