for I in MIN(A,B) .. MAX(C,D) loop
and how we might resolve the overloaded function MIN.
CREATE(FILE => OUTFILE, NAME => "Results");
and what information we should use to determine which CREATE is being invoked:
In this section...
11.5.1 Context of Overload Resolution 11.5.2 Information Used to Resolve Overloading 11.5.3 Ambiguity |
The contexts to be used in overload resolution are given explicitly in RM 8.7. They correspond, we believe, to the natural program fragments that both writer and reader will regard as conceptual units. For example, the controlling expression of a for loop is such a unit: it represents a bounded, ordered iteration over a set of discrete values. Accordingly, the resolution process considers (a), (b), and (c) above: both bounds of the range, and also the fact of its discreteness. We can therefore resolve
for F in ORANGE .. KIWI loop
in the way that we believe the human reader would: as an iteration over fruits.
As another example, consider the case statement
case BIRD_IN_THE_HAND is -- (1) when KIWI => -- (2) ... -- imagine a lot of text here when ORANGE => -- (3) ... ... end case; |
The case expression and the values in the case alternatives must of course all have the same type. However, Ada requires the case expression, on line (1), to be resolved first, before considering the case arms. This is because, otherwise, the human reader would have to scan an arbitrarily large amount of text in order to understand the very first line of the case statement. This would violate our convention (sanctioned by both Ada and natural usage) of linear readability. With the Ada rule, the reader knows at point (2) that KIWI is an APTERON, and knows at point (3) that ORANGE is an error.
The rationale behind the Ada position is threefold. First, the rules should be convenient for, and comprehensible to, the human reader and writer: this must override any consideration of compiler simplicity. Secondly, the rules should allow natural programming conventions to be followed with unsurprising results. Thirdly, the information used should be readily observable in the program text, and not highly implicit.
It seems best to consider first the overloading of operations. The natural use of operator symbols is in infix notation, where clearly the order of the parameters matters, but the formal names do not. And Ada therefore uses the one, and not the other. Thus, given
function "-" (LEFT : TIME; RIGHT : DURATION) return TIME;
then an expression such as
MIDNIGHT - 10*MINUTE
is interpreted as subtracting a DURATION from a TIME (assuming the declaration of MIDNIGHT as a variable of type TIME, and MINUTE as a constant of type DURATION), but
10*MINUTE - MIDNIGHT
will fail: the operands are in the wrong order.
However, Ada does not permit these overloadings:
function "-" (LEFT, RIGHT : INTEGER) return INTEGER; function "-" (MINUEND, SUBTRAHEND : INTEGER) return INTEGER; |
because, even if we did happen to remember the traditional names of the operands, we would never use them in an invocation of the "-" operator. The formal names will never be seen at the point of call, and so cannot be considered in overload resolution.
Since operations are functions, the parameter mode must always be in and so is irrelevant to the resolution. There remains only the question of the result type.
Some languages, such as Algol 68, do not use the result type of an operation to assist in overload resolution. This has the advantage that it leads to an implementation of overload resolution by a single bottom-up traversal of the expression. But is this admitted convenience for the compiler writer accompanied by any benefit for the human programmer?
There are few cases in conventional mathematics where an abstract operation may yield two different types of result, but these cases are significant. One example is the distinction between scalar product and vector product. It is surely desirable to allow
function "*" (LEFT, RIGHT : VECTOR) return SCALAR; function "*" (LEFT, RIGHT : VECTOR) return MATRIX; |
since otherwise one hapless programmer will have to abandon infix notation completely, and the other will have to fight for his monopoly over the "*" symbol.
As another example, consider the rational constructor
function "/" (LEFT, RIGHT : INTEGER) return RATIONAL;
defined above. It is hard to imagine any better way of writing
ALMOST_PI := 355/113;
But this requires the ability to overload "/" on the result type.
We conclude that the use of the function result type in overload resolution is methodologically the better choice, and one that enhances the freedom of the programmer to write natural, comprehensible expressions.
Named procedures and functions, called by conventional prefix notation, present a rather different issue. This is because Ada permits both positional and named parameter association, for the reasons given in section 8.3. Moreover, procedure parameters may take one of three modes: in, in out, out; and in parameters may be defaulted. Potentially, all this information is available for overload resolution.
To resolve overloading, Ada uses the formal names but not the modes; that is, (d), (e) and (f) above, as described in RM 6.6. The reason is simple: the programmer may write the formal names explicitly in the call statement, but has no means of indicating the modes at the place of the call (since all parameter associations use "=>"). Hence, the formal names can be made explicit, but the modes are always implicit, and the natural action is to use explicit information where given, but to avoid using implicit information that the human reader might have difficulty in deducing.
with PALETTE, BOTANY; use PALETTE, BOTANY; procedure P is ... PUT(ORANGE); ... end P; |
contains an ambiguous call of PUT - one that is ambiguous even when all available information is used.
Clearly, the programmer must provide more information. There are two sorts of information that Ada permits one to provide: information about the source of a name, and information about its type. To illustrate the former, consider
BOTANY.PUT(ORANGE);
This is clearly unambiguous, since only one PUT is defined in package BOTANY. Ada dot notation can always be used to give information about the source that provides the name, and, if the package in question has been properly written, this information should suffice. Indeed, this property is essential if packages are to be generally useful software components, since it guarantees that a properly-constructed package can be used by anyone, regardless of what other packages they may need.
To illustrate how type information can be given, consider
PUT(FRUIT'(ORANGE));
This also is unambiguous: ORANGE is a FRUIT, and so the PUT that puts fruits is intended. Since type names cannot be overloaded, and since all expressions can be qualified, this method also ensures overload resolution.
By either of these methods, the user who by accident encounters an ambiguity can make the intended meaning explicit.