As noted in Guideline 4.2, Ada's ability to enforce information hiding
and separation of concerns through its visibility controlling features
is one of the most important advantages of the language. Subverting
these features, for example, by too liberal use of the
use clause, is
wasteful and dangerous.
The Use Clause
- When you need to provide visibility to operators, use the
- Avoid/minimize the use of the
useclause (Nissen and Wallis 1984).
- Consider using a package
renamesclause rather than a
useclause for a package.
- Consider using the
useclause in the following situations:
- When standard packages are needed and no ambiguous references are introduced
- When references to enumeration literals are needed
- Localize the effect of all
This is a modification of the example from Guideline 4.2.3. The effect
use clause is localized:
package Rational_Numbers is
type Rational is private;
function "=" (X, Y : Rational) return Boolean;
function "/" (X, Y : Integer) return Rational; -- construct a rational number
function "+" (X, Y : Rational) return Rational;
function "-" (X, Y : Rational) return Rational;
function "*" (X, Y : Rational) return Rational;
function "/" (X, Y : Rational) return Rational; -- rational division
package body Rational_Numbers is
procedure Reduce (R : in out Rational) is . . . end Reduce;
. . .
package Rational_Numbers.IO is
procedure Put (R : in Rational);
procedure Get (R : out Rational);
procedure Demo_Rationals is
package R_IO renames Rational_Numbers.IO;
use type Rational_Numbers.Rational;
X : Rational_Numbers.Rational;
Y : Rational_Numbers.Rational;
begin -- Demo_Rationals
Put ("Please input two rational numbers: ");
Put ("X / Y = ");
Put (X / Y);
Put ("X * Y = ");
Put (X * Y);
Put ("X + Y = ");
Put (X + Y);
Put ("X - Y = ");
Put (X - Y);
These guidelines allow you to maintain a careful balance between
maintainability and readability. Use of the
use clause may indeed make
the code read more like prose text. However, the maintainer may also
need to resolve references and identify ambiguous operations. In the
absence of tools to resolve these references and identify the impact of
changing use clauses, fully qualified names are the best alternative.
use clause forces you to use fully qualified names. In
large systems, there may be many library units named in
use clauses accompany the
with clauses and the
simple names of the library packages are omitted (as is allowed by the
use clause), references to external entities are obscured and
identification of external dependencies becomes difficult.
In some situations, the benefits of the
use clause are clear. A
standard package can be used with the obvious assumption that the reader
is very familiar with those packages and that additional overloading
will not be introduced.
use type clause makes both infix and prefix operators visible
without the need for
renames clauses. You enhance readability with the
use type clause because you can write statements using the more
natural infix notation for operators. See also Guideline 5.7.2.
You can minimize the scope of the
use clause by placing it in the body
of a package or subprogram or by encapsulating it in a block to restrict
use clause completely can cause problems with enumeration
literals, which must then be fully qualified. This problem can be solved
by declaring constants with the enumeration literals as their values,
except that such constants cannot be overloaded like enumeration
An argument defending the use clause can be found in Rosen (1987).
There are tools that can analyze your Ada source code, resolve
overloading of names, and automatically convert between the
or fully qualified names.
The Renames Clause
- Limit the scope of a renaming declaration to the minimum necessary scope.
- Rename a long, fully qualified name to reduce the complexity if it becomes unwieldy (see Guideline 3.1.4).
- Use renaming to provide the body of a subprogram if this subprogram merely calls the first subprogram.
- Rename declarations for visibility purposes rather than using the use clause, except for operators (see Guideline 5.7.1).
- Rename parts when your code interfaces to reusable components originally written with nondescriptive or inapplicable nomenclature.
- Use a project-wide standard list of abbreviations to rename common packages.
- Provide a
use typerather than a
renamesclause to provide visibility to operators.
procedure Disk_Write (Track_Name : in Track;
Item : in Data) renames
See also the example in Guideline 5.7.1, where a package-level
clause provides an abbreviation for the package
If the renaming facility is abused, the code can be difficult to read. A
renames clause can substitute an abbreviation for a qualifier or long
package name locally. This can make code more readable yet anchor the
code to the full name. You can use the
renames clause to evaluate a
complex name once or to provide a new "view" of an object (regardless
of whether it is tagged). However, the use of
renames clauses can
often be avoided or made obviously undesirable by carefully choosing
names so that fully qualified names read well.
When a subprogram body calls another subprogram without adding local data or other algorithmic content, it is more readable to have this subprogram body rename the subprogram that actually does the work. Thus, you avoid having to write code to "pass through" a subprogram call (Rationale 1995, §II.12).
The list of renaming declarations serves as a list of abbreviation
definitions (see Guideline 3.1.4). As an alternative, you can rename a
package at the library level to define project-wide abbreviations for
packages and then
with the renamed packages. Often the parts recalled
from a reuse library do not have names that are as general as they could
be or that match the new application's naming scheme. An interface
package exporting the renamed subprograms can map to your project's
nomenclature. See also Guideline 5.7.1.
The method described in the Ada Reference Manual (1995) for renaming a type is to use a subtype (see Guideline 3.4.1).
use type clause eliminates the need for renaming infix operators.
Because you no longer need to rename each operator explicitly, you avoid
errors such as renaming a
+ to a
-. See also Guideline 5.7.1.
You should choose package names to be minimally meaningful, recognizing
that package names will be widely used as prefixes (e.g.,
Object : Pkg.Type_Name;). If you rename every
package to some abbreviation, you defeat the purpose of choosing
meaningful names, and it becomes hard to keep track of what all the
For upward compatibility of Ada 83 programs in an Ada 95 environment, the environment includeslibrary-level renamings of the Ada 83 library level packages (Ada Reference Manual 1995, §J.1). It is not recommended that you use these renamings in Ada 95 code.
Limit overloading to widely used subprograms that perform similar actions on arguments of different types (Nissen and Wallis 1984).
function Sin (Angles : in Matrix_Of_Radians) return Matrix;
function Sin (Angles : in Vector_Of_Radians) return Vector;
function Sin (Angle : in Radians) return Small_Real;
function Sin (Angle : in Degrees) return Small_Real;
Excessive overloading can be confusing to maintainers (Nissen and Wallis 1984, 65). There is also the danger of hiding declarations if overloading becomes habitual. Attempts to overload an operation may actually hide the original operation if the parameter profile is not distinct. From that point on, it is not clear whether invoking the new operation is what the programmer intended or whether the programmer intended to invoke the hidden operation and accidentally hid it.
This guideline does not prohibit subprograms with identical names declared in different packages.
- Preserve the conventional meaning of overloaded operators (Nissen and Wallis 1984).
- Use "
+" to identify adding, joining, increasing, and enhancing kinds of functions.
- Use "
-" to identify subtraction, separation, decreasing, and depleting kinds of functions.
- Use operator overloading sparingly and uniformly when applied to tagged types.
function "+" (X : in Matrix;
Y : in Matrix)
Sum := A + B;
Subverting the conventional interpretation of operators leads to confusing code.
The advantage of operator overloading is that the code can become more clear and written more compactly (and readably) when it is used. This can make the semantics simple and natural. However, it can be easy to misunderstand the meaning of an overloaded operator, especially when applied to descendants. This is especially true if the programmer has not applied natural semantics. Thus, do not use overloading if it cannot be used uniformly and if it is easily misunderstood.
There are potential problems with any overloading. For example, if there
are several versions of the
"+" operator and a change to one of them
affects the number or order of its parameters, locating the occurrences
that must be changed can be difficult.
Overloading the Equality Operator
- Define an appropriate equality operator for private types.
- Consider redefining the equality operator for a private type.
- When overloading the equality operator for types, maintain the properties of an algebraic equivalence relation.
The predefined equality operation provided with private types depends on the data structure chosen to implement that type . If access types are used, then equality will mean the operands have the same pointer value. If discrete types are used, then equality will mean the operands have the same value. If a floating- point type is used, then equality is based on Ada model intervals (see Guideline 7.2.7). You should, therefore, redefine equality to provide the meaning expected by the client. If you implement a private type using an access type, you should redefine equality to provide a deep equality. For floating-point types, you may want to provide an equality that tests for equality within some application-dependent epsilon value.
Any assumptions about the meaning of equality for private types will create a dependency on the implementation of that type. See Gonzalez (1991) for a detailed discussion.
When the definition of "
=" is provided, there is a conventional
algebraic meaning implied by this symbol. As described in Baker (1991),
the following properties should remain true for the equality operator:
a = a
a = b ==> b = a
a = b and b = c ==> a = c
In redefining equality, you are not required to have a result type of
Standard.Boolean. The Rationale (1995, §6.3) gives two examples where
your result type is a user-defined type. In a three-valued logic
abstraction, you redefine equality to return one of
Unknown. In a vector processing application, you can define a
component-wise equality operator that returns a vector of Boolean
values. In both these instances, you should also redefine inequality
because it is not the Boolean complement of the equality function.