Although its roots are in Pascal, Ada still borrowed heavily from other languages and was influenced by the latest in software engineering techniques discussed in the 1970's. Undoubtedly the major innovation from that period is the concept of packages. Separation of the specifications from the implementation of 'objects' results in far more flexible code. The containment of the data structures behind a wall of functional interfaces results in much more maintainable systems. This chapter discusses Ada's implementation of packages.
Packages are not about how a program will run, but about how it is constructed and how it is to be understood and maintained.
The package specification of an Ada package describes all the subprogram specifications, variables, types, constants etc that are visible to anyone who 'withs' the package into thier own code.
The following is an example of a package specification.
package odd_demo is type string is array (positive range <>) of character; pi :constant float:=3.14; x: integer; type a_record is record left:boolean; right:boolean; end record; -- note that the following two subprograms are -- specifications only,the body of the subprograms are -- in the body of the package procedure insert(item:in integer; success:out boolean); function present(item:in integer) return boolean; end odd_demo;
The items described in the package specification are often described as resources. We can access these values by 'with'ing the package in our code and then using the package name, a dot, and the name of the resource wanted, whether it is a type declaration, a function or a variable name.
with odd_demo; procedure odder_demo is my_name :odd_demo.string; radius :float; success :boolean; begin radius := 3.0*odd_demo.pi; odd_demo.insert(4, success); if odd_demo.present(34) then ... end odder_demo;
It should be noted that accessing resources in a package is textually identical to accessing the fields in a record.
As accessing the resources of a package using the full dot notation can be cumbersome, the use clause may be employed. When a package is 'use'd in this way the resources are available as though they were declared directly in the code.
with odd_demo; use odd_demo;
procedure odder_demo is
my_name : string(1..10);
radius : float;
success : boolean;
if present(34) then ...
If two packages are with'ed and use'd in the one compilation unit (e.g. subprogram or another package) then there is the possibility of a name clash between the resources in the two packages. In this case the ambiguity can be removed by reverting to dot notation for the affected resources.
package no1 is
package no2 is
with no1; use no1;
with no2; use no2;
procedure clash_demo is
c:=3; -- ambiguous, are we referring to no1.c or
no1.c:=3; -- remove abiguity by reverting to dot
Another problem encountered is when local resources 'hide' the resources in a use'd package. In this case the ambiguity can still be removed by using dot notation.
package no1 is a:integer; end no1; with no1; use no1; procedure p is a:integer; begin a:=4; -- once again this is ambiguous p.a:= 4; -- remove abiguity by using procedure name in -- dot notation no1.a:=5; -- dot notation for package end p;
The package body is where all the implementation details for the services specified in the package specification are placed. As in the specification the package body can contain type declarations, object (variable) declarations, subprograms etc.
The format of a package body is
package body odd_demo is
type list is array (1..10) of integer;
procedure insert(item :in integer;
success :out boolean) is
function present(item:in integer) return boolean is
. . . .
begin -- initialisation statements for the whole package -- These are run _before_ the main program!
for i in storage_list'range loop
The resources in the body of the package are unavailable for use by any other package. Any attempt to reference them will result in a compiler error.
The variables declared in the package body retain their value between successive calls to the public sub procedures. As a result we can create packages that store information for later use.
The begin/end at the end of the package contain initialisation statements for the package. These are executed before the main procedure is run. This is true for all packages. The order in which different package's initialisation statements are run is not defined.
All the resources specified in the package specification are available in the package body without the use of a with clause.
So far all the types declared in the package specification have been completely visible to the user of the package.
Sometimes when creating packages we want to maintain total control over the manipulation of objects. For instance in a package that controls accounts in general ledger, we only want to provide the facilities to make withdrawals, deposits and creation of accounts. No other package needs to know or should have access to the representation details of the account object.
In Ada packages type declarations (and also constants) can be declared private.
package accounts is type account is private; -- declaration comes later procedure withdraw(an_account:in out account; amount :in money); procedure deposit( an_account:in out account; amount :in money); function create( initial_balance:money) return account; function balance( an_account:in account) return integer; private -- this part of the package specification -- contains the full description. type account is record account_no :positive; balance :integer; end record; end accounts;
Outside the package the only operations that can be performed on a private type are assignment, tests for equality and those operations defined by subprograms in the package specification.
Full details of the representation details are available in the package body. Any routine in the package body can access and modify the private type as though it were not private - the privacy of an object applies only outside the package.
It may seem a contradiction to put the private specifications in the package specifications - the public part of the package when you are trying to hide the representation details of an object. This is required for programs that allocate an object of that private type- the compiler then knows how much space to allocate.
Although the reader of the package specifications can see what the representation of the private type really is, there is no way he or she can make explicit use of the knowledge.
Objects can be created outside a package even if of a private type.
with accounts; use accounts; procedure demo_accounts is home_account :account; mortgage :account; this_account :account; begin mortgage := accounts.create(initial_balance => 500.00); withdraw(home_account,50); . . . this_account:=mortgage; -- can assign private types. -- comparing private types. if this_account = home_account then . . . end;
A private type can be made even more private by removing the ability to compare and assign the values outside the package. You generally then have to provide a function to test equality (you can overload the equality operator =, this implicity overloads /= as well). Also you may have to create a procedure to perform assignments.
Q Why do we need private types?
A Consider the following:
type text (maximum_length:positive:=20) is record length: index:=0; value :string(1. . maximum_length); end record;
In this record the length field determines the number of characters in the
field value that have meaning. Any characters from position length+1 to
maximum_length are ignored by us when using the record. However if we ask the
computer to compare two records of this type, it does not know the significance
of the field length; it will compare the length field and all of the value
field. Clearly in this situation we need to write a comparison
Ada95 allows the programmer to override the equality operator for all types.
In some package specifications we may wish to declare a constant of a private type. In the same manner as declaring a private type (and most forward references) we give an incomplete declaration of the constant - the compiler expects the rest to follow in the private section of the package specifications.
package coords is
type coord is private;
home: constant coord; -- the deferred constant!
type coord is record
It was found in large system developments that a single package specification could grow extraordinarily large, with subsequent costs in recompilation of compilation units that depend on it if it were to change. To remedy this situation, the concept of child compilation units was conceived. Child units allow a logically single package to be broken into several physically distinct packages and subprograms. As well as solving the problem of large recompilations, they also provide a convenient tool for providing multiple implementations for an abstract type and for producing self contained subsystems, by using private child units.
A package that consists of a set of declarations may need to be extended at some time. For example a stack package may need the addition of a peek facility. If this package is heavily "withed" by many other units, then modifying the specification to include this extra features could result in a large recompilation despite the fact that most clients would not be using the new feature.
A child package can be declared that logically extends the package, but in a physically separate manner.
package stacks is
type stack is private;
procedure push(onto:in out stack; item:integer);
procedure pop(from :in out stack; item: out integer);
function full(item:stack) return boolean;
function empty(item:stack) return boolean;
-- hidden implementation of stack
-- point A
package stacks.more_stuff is
function peek(item:stack) return integer;
The package stacks.more_stuff is a child packge of stacks. It has the visibility of all the declarations preceeding point A, that is the parent package presents the same visibility to a child package as it does to its body. The child package can see all of its parents private parts. The package bodies would be compiled separately.
The clients who wish to use the function peek can simply have a with clause for the child package,
with stacks.more_stuff; procedure demo is x :stacks.stack; begin stacks.push(x,5); if stacks.more_stuff.peek = 5 then .... end;
'With'ing the child package automatically causes the parent package to be 'with'ed, and its parent package to be 'with'ed, and so on. However the use clause does not act in this way. Visibiliy can only be gained on a package by package basis. This is no doubt a pragmatic decision based on what most organisations would prefer (as indicated by existing use of the 'use' clause).
with stacks.more_stuff; use stacks; use more_stuff; procedure demo is x :stack; begin push(x,5); if peek(x) = 5 then .... end;
A package can have child functions and procedures. Rules for use of these are easily inferred.
Private child units allow a child unit to be created that is only visible within the hierachy of the parent package. In this way facilities for a subsystem can be encapsulated within its a hidden package, with the compilation and visibility benefits that this entails.
Because they are private, a child package's specification is allowed to advertise, in its specification, private parts of its parents. This would not normally be allowed as it would allow the breaking of the hidden implementation of a parent's private parts.
private package stacks.statistics is procedure increment_push_count; end;
The procedure stack.statistics.increment_push_count could be called from within the implementation of the stacks package; this procedure is not available to any clients external to this package hierachy.