In this section...
16.5.1 Overloading PUT and GET |
PUT_CHARACTER PUT_STRING PUT_INTEGER PUT_FLOAT ... PUT_INTEGER_FORMATTED ... |
Thus in Algol 60 we had to write something like
integer I; real X; ... PUTSTRING("results are: "); PUTINT(I); PUTREAL(X); |
whereas in Ada we can much more neatly write
I : INTEGER; X : REAL; ... PUT("results are: "); PUT(I); PUT(X); |
Moreover, without overloading it would be almost impossible to provide facilities for arbitrary user-defined numeric and enumeration types. With overloading we are able to use PUT for the output of all types and all formats; identification of the proper procedure is resolved by the normal rules, and no particular problems arise.
However, even with overloading, there are two limitations of the strict procedural approach. First the language does not have any concept equivalent to the straightening of Algol 68. If straightening were provided, a procedure such as PUT (which is defined for the type INTEGER) would be automatically extended (by iteration) to arrays of integers. Similarly if PUT were defined for the types of the components of a record, it would also be defined for the record type itself. The second limitation concerns parameter lists. It is traditional to perform several output operations with a single statement but this is not permitted by a strictly procedural form. Conceivably one could use another separator (say // ) to permit multiple argument lists for a procedure:
PUT("results are: " // I // X); -- not in Ada
For simplicity neither straightening nor multiple parameter lists have been introduced. The choice of short identifiers such as PUT much reduces the inconvenience of the procedural form.
type REAL is digits 10; R : REAL; |
It is desirable to be able to write
PUT(R);
where the particular PUT has formatting and other properties relevant to the type R, rather than, say,
PUT(FLOAT(R));
which introduces portability problems because the default format will depend upon the properties of the predefined type FLOAT, and this will of course vary with the implementation.
A convenient solution is provided by generic packages that are declared within TEXT_IO and are visible outside of this package. There are four of these, covering integer types, floating types, fixed types, and enumeration types. Each of these packages has one generic parameter that determines the particular type. Thus in the case of floating types the package has the form:
generic type NUM is digits <>; package FLOAT_IO is ... procedure PUT(ITEM : in NUM; ... ); ... end FLOAT_IO; |
The user can then instantiate this generic package in the usual way, and thereby obtain the desired effect:
declare use TEXT_IO; package REAL_IO is new FLOAT_IO(NUM => REAL); use REAL_IO; begin PUT(R); -- calls REAL_IO.PUT ... end; |
The specification of INTEGER_IO is
generic type NUM is range <>; package INTEGER_IO is DEFAULT_WIDTH : FIELD := NUM'WIDTH; DEFAULT_BASE : NUMBER_BASE := 10; ... procedure PUT (ITEM : in NUM; WIDTH : in FIELD := DEFAULT_WIDTH; BASE : in NUMBER_BASE := DEFAULT_BASE); ... end INTEGER_IO; |
For simplicity we have just shown the procedure PUT that takes the default output file; there is another procedure PUT with an additional and leading parameter that gives the file explicitly, as discussed in 16.3.2.
The parameters of PUT are as follows:
ITEM | The numeric value to be output. |
---|---|
WIDTH | The width of the field in which the value is to be placed. The type mark FIELD is just a suitable subtype of INTEGER. The value is right justified in the field, with padding leading spaces and a minus sign if appropriate. If the field is not wide enough, then it is expanded as necessary. |
BASE | The number base to be used. If this is 10 then normal decimal notation is used, otherwise the syntax of based literal is used (with any letters in upper case). Again, the type mark NUMBER_BASE, is a suitable subtype of INTEGER. |
The default expressions for the format parameters should be noted. They are the values of the variables DEFAULT_WIDTH and DEFAULT_BASE which are also declared in the visible part of the generic package INTEGER_IO. The initial value of DEFAULT_WIDTH is the attribute NUM'WIDTH - this gives a field just large enough to hold all values of the type, including a prefixed minus sign or space. The initial value of DEFAULT_BASE is 10.
We now have the desired behavior. The format can be specified on each call, or, alternatively, one or both parameters can be omitted, in which case the relevant defaults will be obtained.
The fact that default expressions for parameters are evaluated on each use is very important. It means that the current values of the variables DEFAULT_WIDTH and DEFAULT_BASE are always used, and not just their initial values. As a consequence, the default values can be changed dynamically. Moreover, since the variables DEFAULT_WIDTH and DEFAULT_BASE are declared in the visible part of the package INTEGER_IO, the user can change them by direct assignment. The user can now write
declare type INDEX is range 0 .. 511; package INDEX_IO is new INTEGER_IO(NUM => INDEX); use INDEX_IO; ... I : INDEX; begin I := 471; PUT(I); -- b471 PUT(I, 6); -- bbb471 PUT(I, BASE => 8); -- 8#727# INDEX_IO.DEFAULT_WIDTH := 7; INDEX_IO.DEFAULT_BASE := 8; PUT(I); -- b8#727# ... end; |
(The letter b in the above comments stands for one blank space.)
Note that INDEX'WIDTH (the initial DEFAULT_WIDTH) is 4; this allows for three digits plus a leading space. It is important to realize that the WIDTH attribute is a property of the subtype and not of the base type - this is very appropriate, because the base type will be derived from one of the predefined types, and could indeed be the type INTEGER; it could not be appropriate to use the WIDTH pertaining to this, since it would vary with the implementation.
Observe also that the package name has been used when assigning to the defaults. This will be necessary if a number of instances of these generic packages have been declared.
In the case of ENUMERATION_IO we have
generic type ENUM is (<>); package ENUMERATION_IO is DEFAULT_WIDTH : FIELD := 0; DEFAULT_SETTING : TYPE_SET := UPPER_CASE; ... procedure PUT (ITEM : in ENUM; WIDTH : in FIELD := DEFAULT_WIDTH; SET : in TYPE_SET := DEFAULT_SETTING); ... end ENUMERATION_IO; |
Here the format parameters control the field width and the case of characters (lower case or upper case, with upper being the norm). TYPE_SET is itself an enumeration type declared in TEXT_IO as follows:
type TYPE_SET is (LOWER_CASE, UPPER_CASE);
The behavior is as expected. Values are by default output in a minimal field - since the default width is zero - and in upper case. Furthermore, the user can provide an explicit format, and also change the default. Padding is by trailing blanks.