Packages
Pure program and library units
Relevant topics
Add link to Preelaboration section
Todo
Complete section!
Package renaming
We've seen in the Introduction to Ada course that we can rename packages.
Grouping packages
A use-case that we haven't mentioned in that course is that we can apply package renaming to group individual packages into a common hierarchy. For example:
Here, we're renaming the Driver_M1
and Driver_M2
packages as
child packages of the Drivers
package, which is a
pure package.
Important
Note that a package that is renamed as a child package cannot refer to
information from its (non-renamed) parent. In other words,
Driver_M1
(renamed as Drivers.M1
) cannot refer to information
from the Drivers
package. For example:
As expected, compilation fails here because Drivers.Counter
isn't
visible in Driver_M1
, even though the renaming (Drivers.M1
)
creates a virtual hierarchy.
Child of renamed package
Note that we cannot create a child package using a parent package name that was
introduced by a renaming. For example, let's say we want to create a child
package Ext
for the Drivers.M1
package we've seen earlier. We
cannot just declare a Drivers.M1.Ext
package like this:
package Drivers.M1.Ext is
end Drivers.M1.Ext;
because the parent unit cannot be a renaming. The solution is to actually extend the original (non-renamed) package:
This works fine because any child package of a package P
is also a child
package of a renamed version of P
. (Therefore, because Ext
is a
child package of Driver_M1
, it is also a child package of the renamed
Drivers.M1
package.)
Backwards-compatibility via renaming
We can also use renaming to ensure backwards-compatibility when changing the package hierarchy. For example, we could adapt the previous source-code by:
converting
Driver_M1
andDriver_M2
to child packages ofDrivers
, andusing package renaming to mimic the original names (
Driver_M1
andDriver_M2
).
This is the adapted code:
Now, M1
and M2
are actual child packages of Drivers
, but
their original names are still available. By doing so, we ensure that existing
software that makes use of the original packages doesn't break.
Private packages
In this section, we discuss the concept of private packages. However, before we proceed with the discussion, let's recapitulate some important ideas that we've seen earlier.
In the Introduction to Ada course, we've seen that encapsulation plays an important role in modular programming. By using the private part of a package specification, we can disclose some information, but, at the same time, prevent that this information gets accessed where it shouldn't be used directly. Similarly, we've seen that we can use the private part of a package to distinguish between the partial and full view of a data type.
The main application of private packages is to create private child packages, whose purpose is to serve as internal implementation packages within a package hierarchy. By doing so, we can expose the internals to other public child packages, but prevent that external clients can directly access them.
As we'll see next, there are many rules that ensure that internal visibility is enforced for those private child packages. At the same time, the same rules ensure that private packages aren't visible outside of the package hierarchy.
Declaration and usage
We declare private packages by using the private
keyword. For example,
let's say we have a package named Data_Processing
:
We simply write private package
to declare a private child package named
Calculations
:
Let's see a complete example:
In this example, we refer to the private child package Calculations
in
the body of the Data_Processing
package — by simply writing
with Data_Processing.Calculations
. After that, we can call the
Calculate
procedure normally in the Process
procedure.
Private sibling packages
We can introduce another private package Advanced_Calculations
as a
child of Data_Processing
and refer to the Calculations
package
in its specification:
Note that, in the body of the Data_Processing
package, we're now
referring to the new Advanced_Calculations
package instead of the
Calculations
package.
Referring to a private child package in the specification of another private
child package is OK, but we cannot do the same in the specification of a
non-private package. For example, let's change the specification of the
Advanced_Calculations
and make it non-private:
Now, the compilation doesn't work anymore. However, we could still refer to
Calculations
packages in the body of the Advanced_Calculations
package:
This works fine as expected: we can refer to private child packages in the body of another package — as long as both packages belong to the same package tree.
Outside the package tree
While we can use a with-clause of a private child package in the body of the
Data_Processing
package, we cannot do the same outside the package tree.
For example, we cannot refer to it in the Test_Data_Processing
procedure:
As expected, we get a compilation error because Calculations
is only
accessible within the Data_Processing
, but not in the
Test_Data_Processing
procedure.
The same restrictions apply to child packages of private packages. For example,
if we implement a child package of the Calculations
package —
let's name it Calculations.Child
—, we cannot refer to it in the
Test_Data_Processing
procedure:
Again, as expected, we get an error because Calculations.Child
—
being a child of a private package — has the same restricted view as its
parent package. Therefore, it cannot be visible in the
Test_Data_Processing
procedure as well. We'll discuss more about
visibility later.
Note that subprograms can also be declared private. We'll see this in another section.
Important
We've discussed package renaming in a previous section. We can rename a package as a private package, too. For example:
Obviously, Drivers.M1
has the same restrictions as any private
package:
As expected, although we can have the Driver_M1
package in a with
clause of the Test_Driver
procedure, we cannot do the same in the
case of the Drivers.M1
package because it is private.
In the Ada Reference Manual
Private with clauses
Definition and usage
A private with clause allows us to refer to a package in the private part of
another package. For example, if we want to refer to package P
in the
private part of Data
, we can write private with P
:
As you can see in the example, as the information from P
is available in
the private part of Data
, we can derive a new type T2
based on
T
from P
. However, we cannot do the same in the visible part of
Data
:
Also, the information from P
is available in the package body. For
example, let's declare a Process
procedure in the P
package and
use it in the body of the Data
package:
In the body of the Data
, we can access information from the P
package — as we do in the P.Process (P.T (A))
statement of the
Process
procedure.
Referring to private child package
There's one case where using a private with clause is the only way to refer to
a package: when we want to refer to a private child package in another child
package. For example, here we have a package P
and its two child
packages: Private_Child
and Public_Child
:
In this example, we're referring to the P.Private_Child
package in the
P.Public_Child
package. As expected, this works fine. However, using a
normal with clause doesn't work in this case:
This gives an error because the information from the P.Private_Child
,
being a private child package, cannot be accessed in the public part of another
child package. In summary, unless both packages are private packages, it's only
possible to access the information from a private package in the private part
of a non-private child package.
In the Ada Reference Manual
Limited Visibility
Sometimes, we might face the situation where two packages depend on
information from each other. Let's consider a package A
that depends
on a package B
, and vice-versa:
Here, we have two
mutually dependent types T1
and T2
, which are declared in two packages A
and B
that
refer to each other. These with clauses constitute a circular dependency, so
the compiler cannot compile either of those packages.
One way to solve this problem is by transforming this circular dependency into
a partial dependency. We do this by limiting the visibility — using a
limited with clause. To use a limited with clause for a package P
, we
simply write limited with P
.
If a package A
has limited visibility of a package B
, then all
types from package B
are visible as if they had been declared as
incomplete types. For the specific case of
the previous source-code example, this would be the limited visibility of
package B
from package A
's perspective:
package B is
-- Incomplete type
type T2;
end B;
As we've seen previously,
we cannot declare objects of incomplete types, but we can declare access types and anonymous access objects of incomplete types. Also,
we can use anonymous access types to declare mutually dependent types.
Keeping this information in mind, we can now correct the previous code by using
limited with clauses for package A
and declaring the component of the
T1
record using an anonymous access type:
As expected, we can now compile the code without issues.
Note that we can also use limited with clauses for both packages. If we do that, we must declare all components using anonymous access types:
Now, both packages A
and B
have limited visibility of each other.
In the Ada Reference Manual
Limited visibility and private with clauses
We can limit the visibility and use
private with clauses at the same time.
For a package P
, we do this by simply writing
limited private with P
.
Let's reuse the previous source-code example and convert types T1
and
T2
to private types:
In this updated version of the source-code example, we have not only limited
visibility of package B
, but also, each package is just visible
in the private part of the other package.
Limited visibility and other elements
It's important to mention that the limited visibility we've been discussing so
far is restricted to type declarations — which are seen as incomplete
types. In fact, when we use a limited with clause, all other declarations have
no visibility at all! For example, let's say we have a package Info
that
declares a constant Zero_Const
and a function Zero_Func
:
Also, let's say we want to use the information (from package Info
) in
package A
. If we have limited visibility of package Info
,
however, this information won't be visible. For example:
As expected, compilation fails because of the limited visibility — as
Zero_Const
and Zero_Func
from the Info
package are not
visible in the private part of A
. (Of course, if we revert to full
visibility by simply removing the limited
keyword from the example, the
code compiles just fine.)
Visibility
Todo
Complete section!
Use type clause
Relevant topics
use type
clause mentioned in Use Clauses
Todo
Complete section!