In this section...
11.4.1 Overloading of Operators |
I, J, K : INTEGER; X, Y, Z : REAL; |
It is then possible to write the statements
K := I*J; Z := X*Y; |
in which the operator symbol "*" refers in the first statement to
function "*" (LEFT, RIGHT : INTEGER) return INTEGER;
and in the second statement to
function "*" (LEFT, RIGHT : REAL) return REAL;
The functions that implement integer multiplication and floating multiplication are represented by the same symbol because they are different implementations of the same abstract operation: the operation of multiplication.
The overloading of predefined operators has been a feature of programming languages ever since Fortran. But Ada also permits users to define new data types, for example COMPLEX or RATIONAL. Since much of the power of the language comes from its extensibility, and since proper use of that extensibility requires that we make as little distinction as possible between predefined and user-defined types, it is natural that Ada also permits new operations to be defined, by declaring new overloadings of the operator symbols. Therefore, since the operation of abstract multiplication applies to complex and rational numbers, one would expect to see
function "*" (LEFT, RIGHT : COMPLEX) return COMPLEX; function "*" (LEFT, RIGHT : RATIONAL) return RATIONAL; |
whereby the programmer can multiply rational or complex numbers using the familiar mathematical notation. The ability to coin descriptive names is an important part of good programming, and it is therefore desirable that a programming language give the programmer as much freedom as possible in the choice of names. Moreover, the use of familiar notation in new contexts is a very powerful descriptive tool: it is an example of the principle of analogy. The ability of an Ada programmer to overload operators upon new types allows the principle of analogy to be used in programming. Further examples of this principle are:
function "*" (LEFT, RIGHT : VECTOR) return SCALAR; function "*" (LEFT, RIGHT : MATRIX) return MATRIX; |
In practice, it is unlikely that two quite different overloadings, such as the two declarations of "*" above, will be defined together. It is more likely that each will be defined in its own package - in this case, one package might be called VECTOR_OPERATIONS and the other SCALAR_OPERATIONS. Similarly, rational multiplication might well be defined in a package
package RATIONAL_ARITHMETIC is type RATIONAL is private; function "+" (RIGHT : RATIONAL) return RATIONAL; function "-" (RIGHT : RATIONAL) return RATIONAL; function "+" (LEFT, RIGHT : RATIONAL) return RATIONAL; function "-" (LEFT, RIGHT : RATIONAL) return RATIONAL; function "*" (LEFT, RIGHT : RATIONAL) return RATIONAL; function "/" (LEFT, RIGHT : RATIONAL) return RATIONAL; function "**" (LEFT : RATIONAL; RIGHT : INTEGER) return RATIONAL; function "/" (LEFT : INTEGER; RIGHT : POSITIVE) return RATIONAL; ... private type RATIONAL is record NUMERATOR : INTEGER; DENOMINATOR : POSITIVE; end record; end RATIONAL_ARITHMETIC; |
In this package, a new type is defined, together with the complete set of applicable operations. The programmer defining the type is free to use the traditional operator symbols for the new type, and to give them a meaning analogous to their meaning with other types. There is no need to worry about other meanings (declarations) that might occur in other packages defining other types: the Ada overloading facility permits the package RATIONAL_ARITHMETIC to be defined as an independent software component.
These operations could be used thus:
CM_PER_INCH : constant RATIONAL := 254/100; -- by international decree! function INCH_TO_CM(INCHES : RATIONAL) return RATIONAL is begin return INCHES * CM_PER_INCH; end; |
Note that, by an analysis similar to the one given in section 9.2.2, we can identify the "/" operation in the expression "254/100" as being the function that takes two integers and yields a rational result.
procedure PUT(X : in STRING); procedure PUT(X : in INTEGER); |
This allows a programmer to write
PUT("The value of X is: "); PUT(X); |
The abstract operation PUT applies indifferently to both strings and integers; it is therefore appropriate that the same name be used in both cases. Observe that this is in accord with the conventions of natural language:
"Put the book on the shelf"
"Put the cat out"
which does not have separate words for putting books and putting cats.
Ada does not permit the overloading of variables or constants. This again is in accordance with traditional habits of thought: we seem far more willing to accept potentially ambiguous names for operations than for things. Thus, mathematicians typically write
I1 + I2 -- integers X1 + X2 -- floating-point values V1 + V2 -- vectors M1 + M2 -- matrices Z1 + Z2 -- complex numbers |
where all the addition operations are written "+" but their operand types are distinguished by a systematic nomenclature. It seems to be a convention of our language that verbs are generic but nouns are specific; Ada reflects this by permitting operations to be overloaded but - normally - not operands. Thus, Ada allows (and we find normal)
procedure SERVE(S : SOUP); procedure SERVE(F : FRUIT); -- permitted overloading |
but does not allow (and which we would find abnormal)
OF_THE_DAY : SOUP; OF_THE_DAY : FRUIT; -- not a legal overloading! |
The first case is called homography: two conceptually different values have the same symbol. It may be illustrated by
package PALETTE is type COLOR is (RED, ORANGE, YELLOW, GREEN, ... ); procedure PUT(X : COLOR); ... end; package BOTANY is type FRUIT is (APPLE, ORANGE, BANANA, KIWI, ... ); procedure PUT(X º FRUIT); ... end; package ORNIT»OLOGY is type APTERON is (MOA, KIWI, OSTRICH, ... ); procedure PUT(X : APTERON); ... end; |
In no sense is a KIWI fruit the same as the flightless KIWI bird: the homography is an accident of language.
A programming language should not forbid such homography: it would be unreasonable to force the author of PALETTE to change the word ORANGE merely because it was a fruit; and indeed Ada never forbids a programmer from defining a locally unambiguous name. But it is a separate design decision whether to permit overloading of such names.
Ada permits overloading of enumeration literals; this is in accord with the idea that an enumeration literal resembles a parameterless function. Hence the following is legal:
with BOTANY, ORNITHOLOGY; use BOTANY, ORNITHOLOGY; procedure P is FRUIT_OF_THE_DAY : FRUIT := KIWI; BIRD_IN_THE_HAND : APTERON := KIWI; ... end; |
Resolution is exactly as for parameterless functions: in the above declarations the required type is evident from the context.
This rule also permits character literals to be used in more than one type:
type ASCII is ( ... , 'A', 'B', 'C', ... ); type EBCDIC is ( ... , 'A', 'B', 'C', ... ); AC : ASCII := 'A'; EC : EBCDIC := 'B'; |
The numeric literals, however, illustrate the second case. In the following:
X : FLOAT := 1.0; Y : LONG_FLOAT := 1.0; |
the two occurrences of "1.0" stand for the same abstract value - unity - but in two different physical representations, and hence, in Ada, associated with two different types. It would be possible to view "1.0" as an overloaded literal - overloaded on all real types. Ada however takes a different view, that we believe corresponds more closely to our intuition. It regards real literals as being all of one type, the type universal_real, and introduces an implicit conversion to the required numeric type. The declarations above are therefore interpreted as
X : FLOAT := FLOAT(1.0); Y : LONG_FLOAT := LONG_FLOAT(1.0); |
The alternative view - that the literals should be considered to be overloaded on all numeric types - would lead to some anomalies, of which the most annoying would perhaps be that
if 1 < 2 then ...
would be ambiguous: would we mean to invoke the "<" of type INTEGER or that of type LONG_INTEGER? The Ada view avoids such difficulties.
Observe by contrast that, if ASCII and EBCDIC are both visible, then
if 'A' < '0' then ...
will indeed be rejected as ambiguous, and rightly so, since the relation means different things in ASCII and EBCDIC.