Ada Programming/Packages

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Ada. Time-tested, safe and secure.
Ada. Time-tested, safe and secure.

Ada encourages the division of code into separate modules called packages. Each package can contain any combination of items.

Some of the benefits of using packages are:

  • package contents are placed in a separate namespace, preventing naming collisions,
  • implementation details of the package can be hidden from the programmer (information hiding),
  • object orientation requires defining a type and its primitive subprograms within a package, and
  • packages that are library units can be separately compiled.

Some of the more common package usages are:

  • a group of related subprograms along with their shared data, with the data not visible outside the package,
  • one or more data types along with subprograms for manipulating those data types, and
  • a generic package that can be instantiated under varying conditions.

The following is a quote from the current Ada Reference Manual Section 7: Packages. RM 7(1) [Annotated]

Packages are program units that allow the specification of groups of logically related entities. Typically, a package contains the declaration of a type (often a private type or private extension) along with the declaration of primitive subprograms of the type, which can be called from outside the package, while their inner workings remain hidden from outside users.

Separate compilation[edit | edit source]

Note: The following chapters deal with packages on library level. This is the most common use, since packages are the basic code structuring means in Ada. However, Ada being a block oriented language, packages can be declared at any level in any declarative region. In this case, the normal visibility rules apply, also for the package body.

Package specifications and bodies on library level are compilation units, so can be separately compiled. Ada has nothing to say about how and where compilation units are stored. This is implementation dependent. (Most implementations indeed store compilation units in files of their own; name suffixes vary, GNAT uses .ads and .adb, APEX .1.ada and .2.ada.) The package body can itself be divided into multiple parts by specifying that subprogram implementations or bodies of nested packages are separate. These are then further compilation units.

One of the biggest advantages of Ada over most other programming languages is its well-defined system of modularization and separate compilation. Even though Ada allows separate compilation, it maintains the strong type checking among the various compilations by enforcing rules of compilation order and compatibility checking. Ada compilers determine the compilation sequence; no make file is needed. Ada uses separate compilation (like Modula-2, Java and C#), and not independent compilation (as C/C++ does), in which the various parts are compiled with no knowledge of the other compilation units with which they will be combined.

A note to C/C++ users: Yes, you can use the preprocessor to emulate separate compilation — but it is only an emulation and the smallest mistake leads to very hard to find bugs. It is telling that all C/C++ successor languages including D have turned away from the independent compilation and the use of the preprocessor.

So it's good to know that Ada has had separate compilation ever since Ada-83 and is probably the most sophisticated implementation around.

Parts of a package[edit | edit source]

A package generally consists of two parts, the specification and the body. A package specification can be further divided in two logical parts, the visible part and the private part. Only the visible part of the specification is mandatory. The private part of the specification is optional, and a package specification might not have a package body—the package body only exists to complete any incomplete items in the specification. Subprogram declarations are the most common incomplete items. There must not be a package body if there is no incomplete declaration, and there has to be a package body if there is some incomplete declaration in the specification.

To understand the value of the three-way division, consider the case of a package that has already been released and is in use. A change to the visible part of the specification will require that the programmers of all using software verify that the change does not affect the using code. A change to the private part of the declaration will require that all using code be recompiled but no review is normally needed. Some changes to the private part can change the meaning of the client code however. An example is changing a private record type into a private access type. This change can be done with changes in the private part, but change the semantic meaning of assignment in the clients code. A change to the package body will only require that the file containing the package body be recompiled, because nothing outside of the package body can ever access anything within the package body (beyond the declarations in the specification part).

A common usage of the three parts is to declare the existence of a type and some subprograms that operate on that type in the visible part, define the actual structure of the type (e.g. as a record) in the private part, and provide the code to implement the subprograms in the package body.

The package specification — the visible part[edit | edit source]

The visible part of a package specification describes all the subprogram specifications, variables, types, constants etc. that are visible to anyone who wishes to use the package.

package Public_Only_Package is

  type Range_10 is range 1 .. 10;

end Public_Only_Package;

Since Range_10 is an integer type, there are a lot of operations declared implicitly in this package.

The private part[edit | edit source]

The private part of a package serves two purposes:

  • To complete the deferred definition of private types and constants.
  • To export entities only visible to the children of the package.
package Package_With_Private is
     
   type Private_Type is private;

private

   type Private_Type is array (1 .. 10) of Integer;

end Package_With_Private;

Since the type is private, clients cannot make any use of it as long as there are no operations defined in the visible part.

The package body[edit | edit source]

The package body defines the implementation of the package. All the subprograms defined in the specification have to be implemented in the body. New subprograms, types and objects can be defined in the body that are not visible to the users of the package.

package Package_With_Body is

   type Basic_Record is private;

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     Integer);

   function Get_A (This : Basic_Record) return Integer;

private

   type Basic_Record is 
      record 
         A : Integer;
      end record ;

   pragma Pure_Function  (Get_A);  -- not a standard Ada pragma
   pragma Inline (Get_A);
   pragma Inline (Set_A);

end Package_With_Body;
package body Package_With_Body is

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     Integer) is
   begin
      This.A := An_A;
   end Set_A;

   function Get_A (This : Basic_Record) return Integer is
   begin
      return This.A;
   end Get_A;

end Package_With_Body;
pragma Pure_Function
Only available when using GNAT.

Two Flavors of Package[edit | edit source]

The packages above each define a type together with operations of the type. When the type's composition is placed in the private part of a package, the package then exports what is known to be an Abstract Data Type or ADT for short. Objects of the type are then constructed by calling one of the subprograms associated with the respective type.

A different kind of package is the Abstract State Machine or ASM. A package will be modeling a single item of the problem domain, such as the motor of a car. If a program controls one car, there is typically just one motor, or the motor. The public part of the package specification only declares the operations of the module (of the motor, say), but no type. All data of the module are hidden in the body of the package where they act as state variables to be queried, or manipulated by the subprograms of the package. The initialization part sets the state variables to their initial values.

package Package_With_Body is

   procedure Set_A (An_A : in Integer);

   function Get_A return Integer;

private

   pragma Pure_Function (Get_A);—not a standard Ada pragma

end Package_With_Body;
package body Package_With_Body is

   The_A: Integer;

   procedure Set_A (An_A : in Integer) is
   begin
      The_A := An_A;
   end Set_A;

   function Get_A return Integer is
   begin
      return The_A;
   end Get_A;


begin

   The_A := 0;

end Package_With_Body;

(A note on construction: The package initialization part after begin corresponds to a construction subprogram of an ADT package. However, as a state machine is an “object” already, “construction” is happening during package initialization. (Here it sets the state variable The_A to its initial value.) An ASM package can be viewed as a singleton.)

Using packages[edit | edit source]

To utilize a package it's needed to name it in a with clause, whereas to have direct visibility of that package it's needed to name it in a use clause.

For C++ programmers, Ada's with clause is analogous to the C++ preprocessor's #include and Ada's use is similar to the using namespace statement in C++. In particular, use leads to the same namespace pollution problems as using namespace and thus should be used sparingly. Renaming can shorten long compound names to a manageable length, while the use type clause makes a type's operators visible. These features reduce the need for plain use.

Standard with[edit | edit source]

The standard with clause provides visibility for the public part of a unit to the following defined unit. The imported package can be used in any part of the defined unit, including the body when the clause is used in the specification.

Private with[edit | edit source]

This language feature is only available from Ada 2005 on.

private with Ada.Strings.Unbounded; 

package Private_With is

   -- The package Ada.String.Unbounded is not visible at this point

   type Basic_Record is private;

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     String);

   function Get_A (This : Basic_Record) return String;

private
   -- The visibility of package Ada.String.Unbounded starts here

   package Unbounded renames Ada.Strings.Unbounded;

   type Basic_Record is 
      record 
         A : Unbounded.Unbounded_String;
      end record;

   pragma Pure_Function  (Get_A);
   pragma Inline (Get_A);
   pragma Inline (Set_A);

end Private_With;
package body Private_With is

   -- The private withed package is visible in the body too

   procedure Set_A (This : in out Basic_Record;
                    An_A : in     String)
   is
   begin
      This.A := Unbounded.To_Unbounded_String (An_A);
   end Set_A;

   function Get_A (This : Basic_Record) return String is
   begin
      return Unbounded.To_String (This.A);
   end Get_A;

end Private_With;

Limited with[edit | edit source]

This language feature is only available from Ada 2005 on.

The limited with can be used to represent two mutually dependent type (or more) located in separate packages.

limited with Departments;

package Employees is

   type Employee is tagged private;

   procedure Assign_Employee
     (E : in out Employee;
      D : access Departments.Department'Class);

   type Dept_Ptr is access all Departments.Department'Class;

   function Current_Department(E : in Employee) return Dept_Ptr;
   ...
end Employees;
limited with Employees;

package Departments is

   type Department is tagged private;

   procedure Choose_Manager
     (Dept    : in out Department;
      Manager : access Employees.Employee'Class);
   ...
end Departments;

Making operators visible[edit | edit source]

Suppose you have a package Universe that defines some numeric type T.

with Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := V * 42.0;  --  illegal
end P;

This program fragment is illegal since the operators implicitly defined in Universe are not directly visible.

You have four choices to make the program legal.

Use a use_package_clause. This makes all declarations in Universe directly visible (provided they are not hidden because of other homographs).

with Universe;
use  Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := V * 42.0;
end P;

Use renaming. This is error prone since if you rename many operators, cut and paste errors are probable.

with Universe;
procedure P is
  function "*" (Left, Right: Universe.T) return Universe.T renames Universe."*";
  function "/" (Left, Right: Universe.T) return Universe.T renames Universe."*";  --  oops
  V: Universe.T := 10.0;
begin
  V := V * 42.0;
end P;

Use qualification. This is extremely ugly and unreadable.

with Universe;
procedure P is
  V: Universe.T := 10.0;
begin
  V := Universe."*" (V, 42.0);
end P;

Use the use_type_clause. This makes only the operators in Universe directly visible.

with Universe;
procedure P is
  V: Universe.T := 10.0;
  use type Universe.T;
begin
  V := V * 42.0;
end P;

There is a special beauty in the use_type_clause. Suppose you have a set of packages like so:

with Universe;
package Pack is
  subtype T is Universe.T;
end Pack;
with Pack;
procedure P is
  V: Pack.T := 10.0;
begin
  V := V * 42.0;  --  illegal
end P;

Now you've got into trouble. Since Universe is not made visible, you cannot use a use_package_clause for Universe to make the operator directly visible, nor can you use qualification for the same reason. Also a use_package_clause for Pack does not help, since the operator is not defined in Pack. The effect of the above construct means that the operator is not nameable, i.e. it cannot be renamed in a renaming statement.

Of course you can add Universe to the context clause, but this may be impossible due to some other reasons (e.g. coding standards); also adding the operators to Pack may be forbidden or not feasible. So what to do?

The solution is simple. Use the use_type_clause for Pack.T and all is well!

with Pack;
procedure P is
  V: Pack.T := 10.0;
  use type Pack.T;
begin
  V := V * 42.0;
end P;

Package organisation[edit | edit source]

Nested packages[edit | edit source]

A nested package is a package declared inside a package. Like a normal package, it has a public part and a private part. From outside, items declared in a nested package N will have visibility as usual; the programmer may refer to these items using a full dotted name like P.N.X. (But not P.M.Y.)

package P is
   D: Integer;

   --  a nested package:
   package N is
      X: Integer;
   private
      Foo: Integer;
   end N;

   E: Integer;
private
   --  another nested package:
   package M is
      Y: Integer;
   private
      Bar: Integer;
   end M;

end P;

Inside a package, declarations become visible as they are introduced, in textual order. That is, a nested package N that is declared after some other declaration D can refer to this declaration D. A declaration E following N can refer to items of N.[1] But neither can “look ahead” and refer to any declaration that goes after them. For example, spec N above cannot refer to M in any way.

In the following example, a type is derived in both of the two nested packages Disks and Books. Notice that the full declaration of parent type Item appears before the two nested packages.

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Shelf is

   pragma Elaborate_Body;

   --  things to put on the shelf

   type ID is range 1_000 .. 9_999;
   type Item (Identifier : ID) is abstract tagged limited null record;
   type Item_Ref is access constant Item'class;

   function Next_ID return ID;
   --  a fresh ID for an Item to Put on the shelf


   package Disks is

      type Music is (
         Jazz,
         Rock,
         Raga,
         Classic,
         Pop,
         Soul);

      type Disk (Style : Music; Identifier : ID) is new Item (Identifier)
         with record
            Artist : Unbounded_String;
            Title  : Unbounded_String;
         end record;

   end Disks;


   package Books is

      type Literature is (
         Play,
         Novel,
         Poem,
         Story,
         Text,
         Art);

      type Book (Kind : Literature; Identifier : ID) is new Item (Identifier)
         with record
            Authors : Unbounded_String;
            Title   : Unbounded_String;
            Year    : Integer;
         end record;

   end Books;

   --  shelf manipulation

   procedure Put (it: Item_Ref);
   function Get (identifier : ID) return Item_Ref;
   function Search (title : String) return ID;

private

   --  keeping private things private

   package Boxes is
      type Treasure(Identifier: ID) is limited private;
   private
      type Treasure(Identifier: ID) is new Item(Identifier) with null record;
   end Boxes;

end Shelf;

A package may also be nested inside a subprogram. In fact, packages can be declared in any declarative part, including those of a block.

Child packages[edit | edit source]

Ada allows one to extend the functionality of a unit (package) with so-called children (child packages). With certain exceptions, all the functionality of the parent is available to a child. This means that all public and private declarations of the parent package are visible to all child packages.

The above example, reworked as a hierarchy of packages, looks like this. Notice that the package Ada.Strings.Unbounded is not needed by the top level package Shelf, hence its with clause doesn't appear here. (We have added a match function for searching a shelf, though):


package Shelf is

   pragma Elaborate_Body;

   type ID is range 1_000 .. 9_999;
   type Item (Identifier : ID) is abstract tagged limited null record;
   type Item_Ref is access constant Item'Class;

   function Next_ID return ID;
   --  a fresh ID for an Item to Put on the shelf

   function match (it : Item; Text : String) return Boolean is abstract;
   --  see whether It has bibliographic information matching Text


   --   shelf manipulation

   procedure Put (it: Item_Ref);
   function Get (identifier : ID) return Item_Ref;
   function Search (title : String) return ID;

end Shelf;

The name of a child package consists of the parent unit's name followed by the child package's identifier, separated by a period (dot) `.'.

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Shelf.Books is

   type Literature is (
      Play,
      Novel,
      Poem,
      Story,
      Text,
      Art);

   type Book (Kind : Literature; Identifier : ID) is new Item (Identifier)
      with record
         Authors : Unbounded_String;
         Title   : Unbounded_String;
         Year    : Integer;
      end record;

   function match(it: Book; text: String) return Boolean;

end Shelf.Books;

Book has two components of type Unbounded_String, so Ada.Strings.Unbounded appears in a with clause of the child package. This is unlike the nested packages case which requires that all units needed by any one of the nested packages be listed in the context clause of the enclosing package (see 10.1.2 Context Clauses - With Clauses (Annotated)). Child packages thus give better control over package dependences. With clauses are more local.

The new child package Shelf.Disks looks similar. The Boxes package which was a nested package in the private part of the original Shelf package is moved to a private child package:

private package Shelf.Boxes is
    type Treasure(Identifier: ID) is limited private;
private
    type Treasure(Identifier: ID) is new Item(Identifier) with null record;
    function match(it: Treasure; text: String) return Boolean;
end Shelf.Boxes;

The privacy of the package means that it can only be used by equally private client units. These clients include private siblings and also the bodies of siblings (as bodies are never public).

Child packages may be listed in context clauses just like normal packages. A with of a child also 'withs' the parent.

Subunits[edit | edit source]

A subunit is just a feature to move a body into a place of its own when otherwise the enclosing body will become too large. It can also be used for limiting the scope of context clauses.

The subunits allow to physically divide a package into different compilation units without breaking the logical unity of the package. Usually each separated subunit goes to a different file allowing separate compilation of each subunit and independent version control history for each one.

 package body Pack is
   procedure Proc is separate;
 end Pack;

 with Some_Unit;
 separate (Pack)
 procedure Proc is
 begin
   ...
 end Proc;

Notes[edit | edit source]

  1. For example, E: Integer := D + N.X;

See also[edit | edit source]

Wikibook[edit | edit source]

Wikipedia[edit | edit source]

Ada 95 Reference Manual[edit | edit source]

Ada 2005 Reference Manual[edit | edit source]