Object-Oriented Software in Ada95

Chapter 5 classes.



In converting this chapter on classes to HTML from the original Microsoft Word file, much of the original layout has been lost and thus this document can only hint at the true format of the original.
These pages use HTML version 3 (Use Netscape 1.1 or later)

5. Packages as classes

This chapter introduces the package construct. A package is an elegant way of encapsulating code and data that interact together into a single unit. A package may be used in a variety of ways, this chapter however will promote it use to define classes.

5.1. Introduction

The world in which we live is populated by many devices and machines that make everyday living easier and more enjoyable. The TV, for instance, is viewed by almost every person in the country, yet few understand exactly what happens inside the box. Likewise, there are many millions of motorists who drive regularly and do not need a detailed knowledge of the workings of a car to make effective use of it.

To many people, their knowledge of a car is as shown in Figure 5.1.

Figure 5.1 Basic understanding of working of an automatic car.

The details of what happens inside the car are not important for most day-to-day driving.

In essence the world is populated with many objects which have an interface that allows the humblest of persons to make effective use of the item. We sometimes criticise the interfaces as being ineffective and difficult to use, yet in most cases we would prefer to use the objects as they stand, rather than having to perform the task by other means.

Likewise in the software world, there are objects that a user or programmer can make effective use of without having to know how the object has been implemented. On a very simple level an Ada program may declare objects to hold floating point numbers, which can then be used with arithmetic operations to sum, multiply, etc. these values. Most programmers however, do not know the exact details of how these operations are performed; they accept the interface provided by the programming language.

At one point it was fashionable for programming languages to provide a rich set of data types. The designers of these languages hoped the data types provided would be adequate for all occasions. The problem was, and still is, that no one language could ever hope to provide all the different types of item that a programmer may need or wish to use.

Ada gives a programmer the ability to declare new data types, together with a range of operations that may be performed on an instance of the type. Naturally, a programmer may also use types and operations on these types that have been defined by other programmers.

5.2. Objects, messages and methods

A car can be thought of as an object. The car contains complex details and processes that are hidden from the driver. For example, to make the car go faster the driver presses the accelerator peddle. The car receives the message go faster and evokes an internal method to speed up the engine.

In the above description of driving a car many object-oriented ideas have been used. These ideas are as follows:

object An item that has a hidden internal structure. The hidden structure is manipulated or accessed by messages sent by a user.
message A request sent to the object to obey one of its methods.
method A set of actions that manipulates or accesses the internal state of the object. The details of these actions are hidden from a user of the object.

5.3. Objects, messages and methods in Ada

In Ada an object is an instance of either a user defined type or an instance of one of the in-built types.

An object for a user defined type can be imagined diagrammatically as Figure 5.2.

Figure 5.2 Diagrammatic representation of an object.

A message is implemented as either a procedure or function call. The body of which is the method that is evoked when the message is sent to the object. The user of the object has no knowledge of the implementation code contained in the body of the procedure of function.

.

5.3.1. An object for a bank account

Before looking in detail at the implementation of an object that represents a bank account, it is appropriate to consider the messages that might be sent to such an object. For a very simple type of bank account these messages would be:

The following program demonstrates the sending of these messages to an instance of an Account.

with Ada.Text_io, Class_account;
use  Ada.Text_io, Class_account;
procedure main is  my_account: Account;
  obtain    : Money;
begin  statement( my_account );
  put("Deposit £100.00 into account"); new_line;
  deposit( my_account, 100.00 );
  statement( my_account );
  put("Withdraw £80.00 from account"); new_line;
  withdraw( my_account, 80.00, obtain );
  statement( my_account );
  put("Deposit £200.00 into account"); new_line;
  deposit( my_account, 200.00 );
  statement( my_account );
end main;

The messages sent to an instance of an Account are: deposit, withdraw, balance, statement . For example, to deposit £100 into my_accountthe following procedural notation is used:

 deposit( my_account, 100.00 );

This should be read as: send the message deposit to the object my_account with a parameter of 100.00.

To withdraw money from the account a programmer would send the message withdraw to the object my_account with two parameters, the amount to withdraw and a variable that is to be filled with the amount actually withdrawn. The implementation of the method will check that the person has sufficient funds in their account to allow the transaction to take place. This is written as:

withdraw( my_account, 80.00, obtain );

5.3.2. Putting it all together

When compiled with an appropriate package body, the above program unit when run will produce the following results:

Mini statement: The amount on deposit is £ 0.00
Deposit £100.00 into account
Mini statement: The amount on deposit is £100.00
Withdraw £80.00 from account
Mini statement: The amount on deposit is £20.00
Deposit £200.00 into account
Mini statement: The amount on deposit is £220.00

5.3.3. Components of a package

The package construct in Ada is split into two distinct parts. These parts contain the following object oriented components:

Ada Package component Object oriented component
Specification The type used to elaborate the object, plus the specification of the message that can be sent to an instance of the type.
Implementation Implementation of the methods that are invoked when a message is sent to the object.

5.3.4. Specification of the package

The specification defines what the packages does, but not how it performs the implementation. It is used by the Ada compiler to check and enforce the correct usage of the package by a programmer.

The specification is split into two distinct parts: a public part and a private part. The public part defines the messages that may be sent to an instance of an Account, whilst the private part defines the representation of the type Account .

As the representation of Account is defined in the private part of the specification, a user of an instance of an Account will not be allowed to access the internal representation. The user however, is allowed to declare and, depending on the description of the type, assign and compare for equality and inequality. In this case the description of the type is private and a user is allowed to declare, assign and compare for equality and inequality instances of Account.

package Class_account is

type Account is private;
subtype Money  is Float;
subtype PMoney is Float range 0.0 .. Float'Last;

procedure statement( the:in Account );
procedure deposit ( the:in out Account; amount:in PMoney );
procedure withdraw( the:in out Account; amount:in PMoney;
                       get:out PMoney );
function  balance ( the:in Account ) return Money;

private
type Account is record
     balance_of : Money := 0.00;      -- Amount in account
end record;
end Class_account;

The component parts of the specification are illustrated in Figure 5.3

Figure 5.3 Components of the specification part of a package.

The representation of Account (which is defined in the private part) is a subtype of a Float that will have an initial value of 0.00. An Ada record groups together several type declarations into a single named type. In this case the record type Account declares a single object called balance_of. Section 6.1 in chapter 6 describes the record type.

5.3.5. Representation of the balance of the account

The package Class_account represents internally the balance of the account as a subtype of a Float. A Floatis an inexact way of representing numbers as only the most significant digits of the number will be stored. Instances of a type declaration of the form type Money is delta 0.01 digits 8; would provide a more reliable way of holding the balance of the account. However, this would require instantiation of a specific package for input and output of objects of this type. To simplify the presentation of this package the representation of the balance of the account is implemented as a subtype of a Float. After reading chapters 13 and 17 the reader may wish to re-implement this package as a generic package which uses an instantiation of Ada.Text_io.Decimal_io.

5.3.6. Implementation of the package

The implementation of the package Class_account is as follows:

with Simple_io; use Simple_io;
package body Class_account is

procedure statement( the:in Account ) is
begin
    put("Mini statement: The amount on deposit is $" );
    put( the.balance_of, aft=>2, exp=>0 );
    new_line(2);
end statement;

procedure deposit ( the:in out Account; amount:in PMoney ) is
begin
    the.balance_of := the.balance_of + amount;
end deposit;

procedure withdraw( the:in out Account; amount:in PMoney;
                      get:out PMoney ) is
begin
  if the.balance_of >= amount then 
      the.balance_of := the.balance_of - amount;
      get := amount;
  else 
      get := 0.00;
  end if;
end withdraw;

function  balance( the:in Account ) return Money is
begin
  return the.balance_of;
end balance;

end Class_account;

The body of the package contains the definition of the procedures and functions defined in the specification part of the package. f Account the . notation is used. For example, in the function balance the result returned is obtained using the statement return the.balance_of; The . notation is used to access a component of an instance of a record type. In this case the instance of the record type is the object the and the component of the object is balance_of.

5.3.7. Terminology

The following terminology is used to describe the components of a class.

Terminology Example:
in class Account
Explanation:
Instance attribute balance_of A data component of an object. In Ada this will be a member of the type that is used to declare the object.
Instance method or just method deposit A procedure or function used to access the instance attributes in an object.

Note: The terminology comes from the language smalltalk.

5.4. The package as seen by a user

A user will normally only have access to the specification part of a package. This provides a specification of the messages that can be sent to an object but does not show how the methods invoked by the messages have been implemented. The implementation part will not normally be available, the implementor normally providing only a compiled version of the package.

Unfortunately the details of the private type will normally be visible, though they can not be accessed.

5.5. The package as seen by an implementor

When building a package the implementor should ensure:

The visibility hierarchy for the package Class_accountis shown in figure 5.4.

Figure 5.4 Visibility of methods and instance attributes of the package Class_account.

5.6. The class

In object oriented programming one of the important ideas is that of the class. A class is the collective name for all objects that share the same structure and behaviour. For example, in a program dealing with bank transactions all the objects that represent a particular type of bank account would belong to the same class.

The class construct in a programming language is used to define objects that share a common structure and behaviour. Ada does not have a class construct.

However, Ada's package construct can be used to simulate the class construct found in other object-oriented programming languages. For example, a class Account is defined by the following package:

package Class_account is

  type Account is private;
  subtype Money  is Float;
  subtype PMoney is Float range 0.0 .. Float'Last;

  procedure statement( the:in Account );
   -- other instance methods
private
  type Account is record
     balance_of : Money := 0.00;      -- Amount in account
  end record;
end Class_account;

package body Class_account is
  -- Implementation of the procedures and functions
end Class_account;

In defining a class the following conventions are used:

5.7. with and use

The clauses with Ada.Text_io; use Ada.Text_io; make available the contents of the package Ada. Text_io to the following program unit. The package Ada.Text_io contains definitions for performing input and output on character and string objects. The exact effect of these clauses are as follows:

Thus without the use class the program to process bank transactions would become:

with Ada.Text_io, Class_account;
procedure main ismy_account:Class_account.Account;
  obtain    :Money;
begin  Class_account.statement( my_account );
  Ada.Text_io.put("Deposit £100.00 into account");
  Ada.Text_io.new_line;
  Class_account.deposit( my_account, 100.00 );
  Class_account.statement( my_account );
  Ada.Text_io.put("Withdraw £80.00 from account");
  Ada.Text_io.new_line;
  Class_account.withdraw( my_account, 80.00, obtain );
  Class_account.statement( my_account );
  Ada.Text_io.put("Deposit £200.00 into account");
  Ada.Text_io.new_line;
  Class_account.deposit( my_account, 200.00 );
  Class_account.statement( my_account );
end main;

Note: Some program guidelines will ban the use of a use clause.

5.7.1. To use or not to use the use clause


Use a use clause Do not use a use clause
Program writing is simplified
Confusion may arise as to which package the item used is a component of.
A program must explicitly state which package the component is taken from. This can reduce the possibility of program error due to accidental misuse.

5.7.2. The package Standard

In Ada the clause with Standard; useStandard; is implicitly added to the start of each program unit. The specification for the package Standardis shown in appendix 0. This package contains definitions for +, -, *, /, etc. The package Standard can not be directly changed by a programmer.

5.7.3. Positioning of with and use in a package declaration

The with and use clauses that appear before a specification of a package are implicitly included for the body of the package. If components of the with'ed and used packages are only used in the body of a package, then the clauses with and use need only be specified for the body. For example, in the class Account the specification of the package Class_accountdoes not use any components of Simple_io and thus can be written as :

package Class_account is  -- rest of specification
end Class_account;

with Simple_io; use Simple_io;
package body Class_account is  -- rest of implementation
end Class_account;

One consequence of this approach is that the user of the package need not know what packages are used by the implementation code.

5.7.4. Conflict in names in a package

A user may wish to use packages that contain items with the same name. For example, a user of the class Class_accountalso requires to use the class Class_Account_other. In both classes the name of the type that is used to declare an instance of the class is Account. By prefixing the type name with the package name the conflict is resolved.

with Ada.Text_io, Class_account, Class_Account_other;
use  Ada.Text_io, Class_account, Class_Account_other;
procedure main is
  my_account    :Class_account.Account;
  other_account :Class_Account_other.Account;
begin
  statement( my_account );             -- In Class_account
  statement( other_account );          -- In Class_Account_other
end main;

Note: Overload resolution is used to resolve which package the procedure statement is implemented in.

5.8. Mutators and inspectors

The methods in a class can either be inspectors, or mutators. The role of each of these types of methods is illustrated in the table below:

Method is a Role of method Example from class Account
Inspector Does not change the state of the object. balance
Mutator Changes the state of the object. withdraw
deposit

5.9. Types private and limited private

In the specification of the class Account seen in section 5.3.4 the type of Account is private. This restricts a user of an instance of the type to the following operations:

A user of the type is prevented from reading or changing the internal contents other than by the actions of methods in the class.

5.9.1. Type limited private

A user can be further restricted in the operations that they can perform on an instance of Account by declaring it as limited private. This removes the users ability to assign or compare an instance of the type by default. Naturally if in the class Account the comparison operations for equality or inequality are provided then these definitions will be used and will override the restriction. For example, if in the class Account the type Account where defined as limited private a user of an instance of an Account would be prevented from writing the following:

procedure devious is
  my_account    : Account;
  other_account : Account;
  get           : Money;
begin
  deposit( my_account, 100.00 );
  other_account := my_account;            -- Copy and
  withdraw( other_account, 100.00, get ); -- Withdraw 100.00
  other_account := my_account;            -- Copy again and
  withdraw( other_account, 100.00, get ); -- Withdraw 100.00
end devious;

If Account in the class Account had been made limited private its specifications would be:

package Class_account is
  type Account is limited private;
  -- Methods (functions and procedures)
private
  type Account is limited record
    the.balance_of : Money := 0.00;     -- Amount in account
  end record;
end Class_account;

Note: That the record declaration in the private part of the class is also of limited type.

In Ada 83 the use of limited in:

type Account is limited record

is not allowed.

The more traditional reason for making a type limited is that a copy operation will not produce the expected result for an instance of the type. Chapter 16 describes such a type that is built using dynamic storage.

5.9.2. Summary

The table below summarizes the allowable uses of private and limited private types.

Operation involving private limited private
Assignment Ok Can not use
Comparison using = and /= by default Ok Can not use
Parameter passing Ok Ok

5.10. Initializing an object at declaration time

In Ada it is possible to initialize an object when it is declared, unfortunately there are restrictions to this initialization. Essentially there are two strategies that can be employed. These strategies are:

For example, the following modified class Account uses both these approaches to initialize an object on declaration.

package Class_account is
  subtype Money  is Float;
  subtype PMoney is Float range 0.0 .. Float'Last;
  type Account( number: Natural:= 0 ) is private;
  procedure statement( the:in Account );
  procedure deposit ( the:in out Account; amount:in PMoney );
  procedure withdraw( the:in out Account; amount:in PMoney;
                       get:out PMoney );
  function  balance ( the:in Account ) return Money;
  procedure new_number( the: in out Account; n:in Natural );
  function  new_account( n:in Natural;
                         amount:in PMoney:=0.0 ) return Account;
private
  type Account( number: Natural:= 0) is record
    balance_of : Float := 0.00;
  end record;
end Class_account;

5.10.1. By discriminant

Here a type can be given a discriminant so that a whole family of types may be declared. The discriminant value is held in an instance of the type. Section 6.4 in Chapter 6 describes in more detail the use of discriminants.

For example, to set my_account with a specific account number the following code is written:

with Ada.Text_io, Class_account;
use  Ada.Text_io, Class_account;
procedure main is
  my_account: Account(10001);
begin
  deposit( my_account, 200.00 );
  statement( my_account );
  new_number( my_account, 10002 );
  statement( my_account );
end main;

Note: The discriminant value 10001 in the declaration of an instance of Account.

Which when run will produce:

Mini statement: Account #      10001
The amount on deposit is £200.00
Mini statement: Account #      10002
The amount on deposit is £200.00

5.10.2. Restrictions

The following restrictions however apply:

Thus the implementation of the procedure new_number that allocates a new account number is:

procedure  new_number( the: in out Account; n: in Natural ) is
begin
    the := Account'( n, the.balance_of );
end new_number;

Note: The whole record structure needs to be changed to change the discriminant.

5.10.3. By assignment

In this case the object is assigned an initial value when it is declared. For example, the following code sets my account with an account number and initial balance.

with Ada.Text_io, Class_account;
use  Ada.Text_io, Class_account;
procedure main is
  my_account : Account := new_account( 10001, 20.0 );
begin
  statement( my_account );
end main;

Which when run will produce:

Mini statement: Account #      10001
The amount on deposit is £20.00

5.10.4. Restrictions

The following restrictions however apply.

In addition the effect of the assignment statement, may have consequences that are undesirable. For an explanation of these consequences see section 0.0.

5.11. A personnel account manager

One of the applications on a PDA (Personnel Digital Assistant) is a PAM (Personnel Account Manager). The PAM provides facilities for recording the transactions that take place on the user's bank account. An example of the use of the PAM is shown below:

[a]  Deposit
[b]  Withdraw
[c]  Balance
Input selection: a
Amount to deposit : 10.00

[a]  Deposit
[b]  Withdraw
[c]  Balance
Input selection: b
Amount to withdraw : 4.60

[a]  Deposit
[b]  Withdraw
[c]  Balance
Input selection: c
Balance is  5. 40

The program can be constructed using two classes: Account shown in section 5.3.4 and a new class TUI that will implement the text interface. The responsibilities of the class TUI are:

Method       Responsibility

menu         Set up the menu that will be displayed to
             the user. Each menu item is described by a
             string.

event        Return the menu item selected by a user of
             the TUI.

message      Display a message to the user.

dialogue     Solicit a response from the user.


The Ada specification of the class TUI is:

package Class_tui is
  type Menu_item is ( M_1, M_2, M_3, M_4, M_QUIT );
  type TUI is private;
  procedure menu( the:in out TUI; m1,m2,m3,m4:in String );
  function  event( the:in TUI ) return Menu_item;
  procedure message( the:in TUI; mes:in String );
  procedure dialogue(the:in TUI; mes:in String; res:out Float);
  procedure dialogue(the:in TUI; mes:in String; res:out Integer);
private
  -- Not a concern of the client of the class
end Class_tui;

For example, if an instance of the TUI had been declared with:

screen : TUI;

Then, to set-up the menu system:

[a]  Print
[b]  Calculate
Input selection:

the following code sequence would be used:

menu( screen, "Print", "Calculate", "", "" );

Note: Null or empty menu items are not displayed.

A string may be of any length, however to store a string the receiving object must be of the correct size. Ada strings are fully discussed in section 7.8.

The user's response to this menu is elicited with the function event. The function event returns an enumeration representing the menu item selected. For example, if the user selected option [b] then the code:

  case event( screen ) is
    when M_1 =>                       -- Print
    when M_2 =>                       -- Calculate

associated with label M_2 would be obeyed.

Note: The selected menu item is indicated by an enumeration M_1 for menu item 1, M_2 for menu item 2, etc.

A programmer can display a message onto the TUI by using the procedure message which has the text to be output as its second parameter. Likewise a programmer can initiate a dialogue with the user by using the procedure dialogue that returns a floating point number. The TUI currently only supports dialogues that solicit a floating point number or integer number.

The fragment of code below illustrates the use of message and dialogue interactions in a program which converts miles to kilometres..

message ( screen, "Distance converter" );
dialogue( screen, "Enter distance in miles", miles );
message ( screen, "Distance in kilometres is " &
                  Float'Image( miles * 1.6093 ) );

Note: The operator & concatenates two strings into a single string. For example, "Hello" & " " & "world" delivers the single string "Hello world".

In constructing the main program for the personnel account manager, a nested function float_image is used to simplify the construction of the program.

with Simple_io, Class_account, Class_tui;
use  Simple_io, Class_account, Class_tui;
procedure main is
  user     : Account;            -- The users account
  screen   : TUI;                -- The display screen
  cash     : Money;              --
  received : Money;              --

The nested function float_image converts a floating point number into an Ada string. This function is provided so that the format of the number may be controlled.

  function float_image( f:in Float ) return String is 
    res : String( 1 .. 10 );         -- String of 10 characters
  begin
    put( res, f, aft=>2, exp=>0 );   -- 2 digits - NO exp
    return res;
  end float_image;

Note: The declaration of a string of 10 characters, which is filled with the character representation for the floating point number res.

The procedure 'put( res, f, aft=>2, exp=>0 );' which converts a floating point number into a string.

The main body of the program processes the option selected by the user.

begin
  loop
    menu( screen, "Deposit", "Withdraw", "Balance", "" );
    case event( screen ) is
      when M_1 =>                                    -- Deposit
        dialogue( screen, "Amount to deposit", cash );
        if cash <= 0.0 then
          message( screen, "Must be >= 0.00" );
        else
          deposit( user, cash );
        end if;
      when M_2 =>                                     -- Withdraw
        dialogue( screen, "Amount to withdraw", cash );
        if cash <= 0.0 then
          message( screen, "Must be >= 0.00" );
        else
          withdraw( user, cash, received );
          if received <= 0.0 then
            message( screen, "Not enough money" );
          end if;
        end if;
      when M_3 =>                                    -- Balance
         message( screen, "Balance is " &
                            float_image( balance(user)) );
      when M_QUIT =>                                 -- Exit
         return;
      when others =>                                 -- Not used
          message( screen, "Program error");         -- oops
    end case;
  end loop;
end main;

5.12. Class TUI

The full specification for the class TUI is:

package Class_tui is
  type Menu_item is ( M_1, M_2, M_3, M_4, M_QUIT );
  type TUI is private;
  procedure menu( the:in out TUI; m1,m2,m3,m4:in String );
  function  event( the:in TUI ) return Menu_item;
  procedure message( the:in TUI; mes:in String );
  procedure dialogue(the:in TUI; mes:in String; res:out Float);
  procedure dialogue(the:in TUI; mes:in String; res:out Integer);
private
  type TUI is record
     selection : Menu_item := M_QUIT;
  end record;
end Class_tui;

In the implementation of the class TUI the most complex method is menu. This method is implemented as a procedure that writes out the menu for the TUI and reads the users response. It will only complete when a valid response has been received from the user. In the implementation of the procedure the technique of procedural decomposition is used to simplify the code.

In procedural decomposition a large body of code is split into several procedures or functions. This helps to reduce complexity making construction and maintenance easier.

with Ada.Text_io; use Ada.Text_io;
package body Class_tui is
  procedure menu( the:in out TUI; m1,m2,m3,m4:in String ) is
  selection      : Character;
  valid_response : Boolean := FALSE;

As a user may inadvertently select a null menu item, the procedure set_response is used to disallow such an action.

  procedure set_response(choice:in Menu_item; mes:in String) is
  begin
    if mes /= "" then               -- Allowable choice
      the.selection := choice; valid_response := TRUE;
    end if;
  end set_response;

The procedure display_menu_item displays onto the TUI only non null menu items.

  procedure display_menu_item(prompt, name:in String) is
  begin
      if name/="" then put(prompt & name); new_line(2); end if;
  end display_menu_item;

The main body of the procedure displays the menu on the screen and receives the selected menu choice from the user. If an invalid response is received the menu is re-displayed and the user is asked again to select a menu item.

  begin
    while not valid_response loop
      display_menu_item( "[a]  ", m1 );
      display_menu_item( "[b]  ", m2 );
      display_menu_item( "[c]  ", m3 );
      display_menu_item( "[d]  ", m4 );
      put( "Input selection: "); get( selection ); skip_line;
      case selection is
        when 'a' | 'A' => set_response( M_1, m1 );
        when 'b' | 'B' => set_response( M_2, m2 );
        when 'c' | 'C' => set_response( M_3, m3 );
        when 'd' | 'D' => set_response( M_4, m4 );
        when 'e' | 'E' => set_response( M_QUIT, "Quit" );
        when others    => valid_response := FALSE;
      end case;
      if not valid_response then
        message( the, "Invalid response" );
      end if;
    end loop;
  end menu;

The function event returns the user's selection.

  function  event( the:in TUI ) return Menu_item is
  begin
    return the.selection;
  end;

The procedure message writes a string onto the screen.

  procedure message( the:in TUI; mes:in String ) is
  begin
    new_line; put( mes ); new_line;
  end message;

The procedure dialogue solicits a response from the user.

  procedure dialogue(the:in TUI; mes:in String; res:out Float) is
  begin
    new_line(1); put( mes & " : " );
    get( res ); skip_line;
  end dialogue;
  procedure dialogue(the:in TUI; mes:in String;
                     res:out Integer) is
  begin
    new_line(1); put( mes & " : " );
    get( res ); skip_line;
  end dialogue;
end Class_tui;

Note: In this case the response must be a floating point number or an integer number. Other overloaded procedures can be provided for different forms of dialogue.


© M.A.Smith University of Brighton. Created September 1995 last modified February 1997.
Comments, suggestions, etc. M.A.Smith@brighton.ac.uk * [Home page]