As mentioned in the
Introduction (see Section 1.3.1), the Ada
95 object oriented model has been criticized for not being really OO
since the notation for applying a subprogram (method) to an object emphasizes
the subprogram and not the object. Thus given

...

then we usually have
to write

P.Op(Y, ... ); -- *subprogram first*

in order to apply the
operation to an object Y of type T
whereas an OO person would expect to write

Y.Op( ... ); -- *object first*

Some hard line OO languages such as Smalltalk take
the view that everything is an object and that all activities are operations
upon some object. Thus adding 2 and 3 can be seen as sending a message
to 2 instructing 3 to be added to it. This is clearly an extreme view.

Older languages take
the view that subprograms are dominant and that they act upon parameters
which might be raw numbers such as 2 or denote objects such as a circle.
Ada 95 primarily takes this view which reflects its Pascal foundation
over 20 years ago. Thus if Area is a function
which returns the area of a circle then we write

A := Area(A_Circle);

However, when we come
to tasks and protected objects Ada takes the OO view in which the identity
of the object comes first. Thus given a task Actor
with an entry Start we call the entry by writing

Actor.Start( ... );

So Ada 95 already uses the object notation although
it only applies to concurrent objects such as tasks. Other objects and,
in particular, objects of tagged types have to use the subprogram notation.

A major irritation
of the subprogram notation is that it is usually necessary to name the
package containing the declaration of the subprogram thus

P.Op(Y, ... ); -- *package P mentioned*

There are two situations when P
need not be mentioned – one is where the procedure call is actually
inside the package P, the other is where we
have a use clause for P (and even that sometimes
does not give the required visibility). But these are special cases.

Y.Op( ... ); -- *package P never mentioned*

provided that

T is a tagged type,

Op is a primitive (dispatching)
or class wide operation of T,

Y is the first parameter
of Op.

The reason there is never any need to mention the
package is that, by starting from the object, we can identify its type
and thus the primitive operations of the type. Note that a class wide
operation can be called in this way only if it is declared at the same
place as the primitive operations of T (or
one of its ancestors). The parameter Y need
not be simply the name of an object. It can be anything allowed as a
parameter such as a dereference or a function call. But the type T
must be tagged.

There are many advantages of the prefixed notation
as we shall see but perhaps the most important is ease of maintenance
from not having to mention the package containing the declaration of
the operation. Having to name the package is often tricky because in
complicated situations involving several levels of inheritance it may
not be obvious where the operation is declared. This happens especially
when operations are declared implicitly and when class-wide operations
are involved. Moreover if we change the structure for some reason then
operations might move.

As a simple example consider a hierarchy of plane
geometrical object types. All objects have a position given by the two
coordinates *x* and *y* (this is the position of the centre
of gravity of the object). There will be other specific properties according
to the type such as the radius of a circle. In addition there might be
general properties such as the area of the object, its distance from
the origin and moment of inertia about its centre.

There are a number of ways in which such a hierarchy
might be structured. We might have a package declaring a root abstract
type and then another package with several derived types.

X_Coord: Float;

Y_Coord: Float;

This package declares
the root type and two abstract operations Area
and MI (moment of inertia) and a concrete
operation Distance. We might then have

Radius: Float;

A, B, C: Float; --

-- *and so on for other types such as Square*

(In the following discussion we will assume that
use clauses are not being used. This is quite realistic because many
projects forbid use clauses.)

Having declared some
objects such as A_Circle and A_Triangle
we can then apply the operations Area, Distance,
and MI. In Ada 95 we write

A := Shapes.Area(A_Circle);

D := Shapes.Distance(A_Triangle);

M := Shapes.MI(A_Square);

D := Shapes.Distance(A_Triangle);

M := Shapes.MI(A_Square);

Observe that the operation
Distance is inherited and so is implicitly
declared in the package Shapes for all types
even though there is no mention of it in the text of the package Shapes.
However, if we were using Ada 2005 and the prefixed notation then we
could simply write

A := A_Circle.Area;

D := A_Triangle.Distance;

M := A_Square.MI;

D := A_Triangle.Distance;

M := A_Square.MI;

and there is no mention of the package Shapes
at all.

A clever friend then
points out that by its nature Distance is
the same for all types so it would be safer to avoid the risk of it getting
changed by making it class wide. So we change the declaration of Distance
in the package Root thus

and recompile our program.
But the Ada 95 version won't recompile. Why? Because class wide operations
are not inherited. So there is only one function Distance
and it is declared in the package Root. So
all our calls of Distance have to be changed
to

D := Root.Distance(A_Triangle);

However, if we had been using the prefixed notation
then there would have been nothing to change.

Our manager might then
read about the virtues of child packages and tell us to restructure the
whole thing as follows

... -- *functions Area, MI, Distance*

**end** Geometry;

Radius: Float;

... -- *functions Area, MI*

**end** Geometry.Circles;

A, B, C: Float;

... -- *functions Area, MI*

**end** Geometry.Triangles;

-- *and so on*

This is of course a
much more beautiful structure and avoids having to write Root.Object
when doing the extensions. But, horrors, our assignments in Ada 95 now
have to be changed to

A := Geometry.Circles.Area(A_Circle);

D := Geometry.Distance(A_Triangle);

M := Geometry.Squares.MI(A_Square);

D := Geometry.Distance(A_Triangle);

M := Geometry.Squares.MI(A_Square);

But the lucky programmer
using Ada 2005 can still write

A := A_Circle.Area;

D := A_Triangle.Distance;

M := A_Square.MI;

D := A_Triangle.Distance;

M := A_Square.MI;

and have a refreshing coffee (or a relaxing martini)
while we are toiling with the editor.

Some time later the
program might be extended to accommodate triangles that are specialized
to be equilateral. This might be done by

...

...

This type of course
inherits all the operations of the type Triangle.
We might now realize that the object A_Triangle
of type Triangle was equilateral anyway and
so it would be better to change it to be of type Equilateral_Triangle.
The lucky Ada 2005 programmer will only have to change the declaration
of the object but the poor Ada 95 programmer will have to change the
calls on all its primitive operations such as

A := Geometry.Triangles.Area(A_Triangle);

to the corresponding

A := Geometry.Triangles.Equilateral.Area(A_Triangle);

Other advantages of
the prefixed notation were mentioned in the
Introduction. One is that it unifies the notation for calling a function
with a single parameter and directly reading a component of the object.
Thus we can write uniformly

X := A_Circle.X_Coord;

A := A_Circle.Area;

A := A_Circle.Area;

Of course if we were foolish and had a *visible*
component Area as well as a function Area
then we could not call the function in this way.

But now suppose we
decide to make the root type private so that the coordinates cannot be
changed inadvertently. Moreover we decide to provide functions to read
them. So we have

X_Coord: Float;

Y_Coord: Float;

Using Ada 95 we would
now have to change statements such as

X := A_Triangle.X_Coord;

Y := A_Triangle.Y_Coord;

Y := A_Triangle.Y_Coord;

into

X := Geometry.X_Coord(A_Triangle);

Y := Geometry.Y_Coord(A_Triangle);

Y := Geometry.Y_Coord(A_Triangle);

or (if we had not been
wise enough to make the functions class wide) perhaps even

X := Geometry.Triangles.Equilateral.X_Coord(A_Triangle);

Y := Geometry.Triangles.Equilateral.Y_Coord(A_Triangle);

Y := Geometry.Triangles.Equilateral.Y_Coord(A_Triangle);

whereas in Ada 2005 we do not have to make any changes
at all.

Another advantage mentioned
in the Introduction is that when using access types explicit dereferencing
is not necessary. Suppose we have

...

This_One: Pointer := A_Circle'Access;

In Ada 95 (assuming
that X_Coord is a visible component) we have
to write

Put(This_One.X_Coord); ...

Put(This_One.Y_Coord); ...

Put(Geometry.Area(This_One.**all**));

Put(This_One.Y_Coord); ...

Put(Geometry.Area(This_One.

whereas in Ada 2005
we can uniformly write

Put(This_One.X_Coord); ...

Put(This_One.Y_Coord); ...

Put(This_One.Area);

Put(This_One.Y_Coord); ...

Put(This_One.Area);

and of course this remains unchanged if we make the
coordinates into functions whereas the Ada 95 statements will need to
be changed.

There are other structural changes that can occur
during program development which are much easier to cope with using the
prefix notation. For example, a class wide operation might be moved.
And in the case of multiple interfaces to be described in the next section
an operation might be moved from one interface to another.

It is clear that the prefixed notation has significant
benefits both in terms of program clarity and for program maintenance.

Other variations on the rules for the use of the
notation were considered. One was that the mechanism should apply to
untagged types as well but this was rejected on the grounds that it might
add to rather than reduce confusion in some cases. In any event, untagged
types do not have class wide types so they are intrinsically simpler.
It would have been particularly confusing to permit the notation to apply
to access types especially an access type A
referring to a tagged type T. If the access
type and the tagged type both had the same or similar operations Op
then ambiguities or errors could easily arise.

It is of course important
to note that the first parameter of an operation plays a special role
since in order to take advantage of the prefixed notation we have to
ensure that the first parameter is a controlling parameter. Treating
the first parameter specially can appear odd in some circumstances such
as when there is symmetry among the parameters. Thus suppose we have
a set package for creating and manipulating sets of integers

...

then we can apply the
function Union in the traditional way

A, B, C: Set;

...

C := Sets.Union(A, B);

...

C := Sets.Union(A, B);

The object oriented
addict can also write

C := A.Union(B);

but this destroys the obvious symmetry and is rather
like sending 3 to be added to 2 mentioned at the beginning of this discussion.

Hopefully the mature
programmer will use the OO notation wisely. Maybe its existence will
encourage a more uniform style in which the first parameter is always
a controlling operand wherever possible. Of course it cannot be used
for functions which are tag indeterminate such as

since there are no controlling parameters. If a subprogram
has just one parameter (which is controlling) such as Size
then the call just becomes X.Size and no parentheses
are necessary.

Remember that the prefix
does not have to be simply the name of an object such as A_Circle
or an implicit dereference such as This_One,
it could be a function call so we might write

N := Sets.Empty.Size; -- *N = 0*

M := Sets.Unit(99).Size; --*M = 1*

M := Sets.Unit(99).Size; --

with the obvious results as indicated.

© 2005, 2006, 2007 John Barnes Informatics.

Sponsored in part by:

The Ada Resource Association and its member companies: |
and Ada-Europe: |