In this section...
9.2.1 Named Collections of Entities
9.2.2 Groups of Related Subprograms
9.2.3 Private Types
package PLOTTING_DATA is PEN_UP : BOOLEAN := TRUE; CONVERSION_FACTOR, X_OFFSET, Y_OFFSET, X_MIN, X_MAX, Y_MIN, Y_MAX : REAL; X_VALUE : array(1 .. 500) of REAL; Y_VALUE : array(1 .. 500) of REAL; end PLOTTING_DATA;
The elaboration of this package consists of the elaboration of its constituent variable declarations. Elaboration takes place in the context where the package declaration appears textually. Thus, in terms of the lifetime of the constituent variables such as PEN_UP and Y_VALUE, everything happens as if their declarations were inserted in the place of the declaration of the package PLOTTING_DATA.
The constituent variables are not, however, automatically visible outside the package: steps must be taken to render them visible. In any context where the package is itself visible, it is possible to acquire visibility (by selection) of such a variable by an expanded name, written with the dot notation. For example, we could write statements such as
PLOTTING_DATA.PEN_UP := TRUE; PLOTTING_DATA.X_VALUE(10) := PLOTTING_DATA.X_MIN;
In the expanded name PLOTTING_DATA.PEN_UP, the variable PEN_UP is visible by selection after the dot following PLOTTING_DATA: in this sense the dot notation opens up the visibility of one variable at a time. It is also possible to acquire direct visibility of all these variables at once by means of a use clause such as
The effect of the use clause is that all variables declared within the package become directly visible (unless they would conflict with other names already visible). The simple name, and the meaning, of each variable is then as defined in the package. For example, the previous statements can be rewritten more concisely as follows:
declare use PLOTTING_DATA; begin PEN_UP := TRUE; X_VALUE(10) := X_MIN; end;
This simple form of package corresponds closely to the notion of a named common block in Fortran. There are however three crucial differences between this use of packages and Fortran named common blocks:
package METRIC_CONVERSIONS is CM_PER_INCH : constant := 2.54; CM_PER_FOOT : constant := 12*CM_PER_INCH; CM_PER_YARD : constant := 3*CM_PER_FOOT; KM_PER_MILE : constant := 1.609_344; end METRIC_CONVERSIONS;
More generally, in a typed language, groups of entities are likely to include logically related types, along with constants and variables, as shown in the following example:
package WORK_DATA is type DAY is (MON, TUE, WED, THU, FRI, SAT, SUN); type HOURS_SPENT is delta 0.25 range 0.0 .. 24.0; type TIME_TABLE is array (DAY) of HOURS_SPENT; WORK_HOURS : TIME_TABLE; NORMAL_HOURS : constant TIME_TABLE := (MON .. THU => 8.25, FRI => 7.0, SAT | SUN => 0.0); end WORK_DATA;
In all three examples we achieve the same effect: the elaboration of the package creates the corresponding entities (whether they be constants, variables, or types). But these entities are not automatically externally visible: external visibility is obtained only by an expanded name (dot notation) or by a use clause. Thus in a context that has a use clause for WORK_DATA we may declare variables of type HOURS_SPENT, update the array WORK_HOURS, and read the constant NORMAL_HOURS.
declare use WORK_DATA; TODAY : DAY; HOURS : HOURS_SPENT; begin -- compute HOURS and TODAY ... if HOURS > NORMAL_HOURS (TODAY) then HOURS := 2*HOURS - NORMAL_HOURS(TODAY); end if; WORK_HOURS (TODAY) := HOURS; end;
Declaring such functions within a package (say MATH_FUNCTIONS) is certainly preferable to having them be predefined functions in the standard environment. Thereby, a user who is not dealing with numerical computations does not have to refer to MATH_FUNCTIONS, and his name space - the set of names that must be remembered - will not be congested by names that are useless to him or restricted by names that he might wish to use differently.
We next consider a package for table management - an example that will enable us to point out other important possibilities. It is made of two parts: The first part is the package specification and its structure is as follows:
package TABLE_MANAGER is -- the visible part end TABLE_MANAGER;
The package specification defines the visible part of the package; that is, the declarations that become directly visible in a context that has a use clause for TABLE_MANAGER. In the present case, this user interface consists of the declaration of the type ITEM and of the three procedures INSERT (to insert an item into the table), RETRIEVE (to retrieve the first item from the table), and DISPLAY (to display the current contents of the table), as shown below:
package TABLE_MANAGER is type ITEM is record -- the components of each item end record; procedure INSERT (NEW_ITEM : in ITEM); procedure RETRIEVE (FIRST_ITEM : out ITEM); procedure DISPLAY; end TABLE_MANAGER;
The second part of the package is the package body. This encloses the hidden part of the package: none of the entities contained therein is visible outside the package (the only entities that can be made visible by expanded names or by use clauses are those of the visible part). The structure of the package body is as follows:
package body TABLE_MANAGER is -- hidden data and subprogram bodies begin -- statements for initialization end TABLE_MANAGER;
In the formulation of this package body given below, each item is put in a cell: hence we have the declaration of a local type called CELL. The table itself is a local variable, called TABLE and declared as an array of cells. The fact that this declaration is local to the package body ensures that reading and updating of the table is possible only from within this body. The table is initialized by the statements at the end of the package body, and its value can be read and updated by the subprogram bodies that appear within the package body. Finally the package body contains the bodies of the procedures INSERT, RETRIEVE, and DISPLAY, as well as two local functions.
package body TABLE_MANAGER is type CELL is ... ; -- a local type subtype INDEX is ... ; -- a local subtype TABLE : array (INDEX) of CELL; -- a local variable function NEXT return INDEX is -- a local function begin -- computes the index to the next cell end; function STORE(N : ITEM) return CELL is -- a local function begin -- returns a cell containing N end; procedure INSERT(NEW_ITEM : in ITEM) is begin TABLE(NEXT) := STORE(NEW_ITEM); end; procedure RETRIEVE(FIRST_ITEM : out ITEM) is ... end; procedure DISPLAY is ... end; begin -- statements for the initialization of TABLE end TABLE_MANAGER;
The two parts of a package (the package specification and the package body) are always distinct. They need not even be textually contiguous, and may indeed be compiled separately. In this way the contents of a package body are not only hidden logically, but can also be hidden physically (as discussed in section 9.3.3 below).
Another example of a package containing a type declaration and functions defining operations for this type is a variation of the package RATIONAL_NUMBERS given in the Reference Manual (section 7.3). The specification of this package is as follows:
package RATIONAL_NUMBERS is type RATIONAL is record NUMERATOR : INTEGER; DENOMINATOR : POSITIVE; end record; function EQUAL(X, Y : RATIONAL) return BOOLEAN; function "/" (X : INTEGER; Y : POSITIVE) return RATIONAL; -- to 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; ... end;
The type RATIONAL is declared within the visible part of the package. In a context that contains a use clause for RATIONAL_NUMBERS it is possible to declare variables of type RATIONAL and to apply the operators "+", "-", "*", "/", and the function EQUAL to them. The operator "/" with integer arguments allows rational values to be written in the conventional form. For example:
declare use RATIONAL_NUMBERS; A : RATIONAL := 3/31; B : RATIONAL := 7/100; C : RATIONAL; begin ... C := A*B; C := C + 5/17; ... end;
Consider for example the initialization of A with 3/31. The "/" operation must be applicable to integer literals and yield a value of type RATIONAL. The only one to do so is the division declared in the visible part with two parameters of type INTEGER. Hence the integer literals 3 and 31 are implicitly converted to this type and the division is applied. The body of this function will be provided in the package body. For example, it could be written in the following way, which involves no arithmetic and is exact:
function "/" (X : INTEGER; Y : POSITIVE) return RATIONAL is begin return RATIONAL'(NUMERATOR => X; DENOMINATOR => Y); end "/";
Note also that a user could also write a rational value directly, as an aggregate:
C := C + RATIONAL'(NUMERATOR => 5, DENOMINATOR => 15);
Hence, with this formulation, it remains possible to operate directly on the components of a rational number and to construct rational values as record aggregates. This could be considered a weakness of the formulation. For instance, the algorithms used for all the operations on rational numbers may maintain them in a canonical form (where no further reduction is possible); but users could create noncanonical rationals by operating directly on the record components. The third form of package, presented in the next section, deals with such issues.
Private types cater for situations in which we want the name of a type to be public, but the knowledge of the internal properties to be available only to the subprogram bodies contained in the package body. This encapsulation is achieved by declaring the type name (alone) within the visible part - since the type name is to be available to users of the package - but at the same time specifying the type to be private; the full definition of the type (showing its structure) is then provided following the visible part.
As an example of the use of private types, consider the following skeleton of the declaration of an input-output package:
package SIMPLE_IO is type FILE_NAME is private; NO_FILE : constant FILE_NAME; procedure CREATE (FILE : out FILE_NAME; NAME : in STRING); procedure READ (ITEM : out INTEGER; FILE : in FILE_NAME); procedure WRITE (ITEM : in INTEGER; FILE : in FILE_NAME); private type FILE_NAME is new INTEGER range 0 .. 50; NO_FILE : constant FILE_NAME := 0; end SIMPLE_IO;
In the visible part given above, the type FILE_NAME is declared as private. External to the package it is possible to declare variables of the type FILE_NAME, but the properties of objects of this type are kept private. Hence the only things a user can do with file names is assign them to other file-name variables, compare them for equality, obtain them by calling the procedure CREATE, or pass them as parameters to the procedures READ and WRITE.
The full definition of the private type FILE_NAME and that of the deferred constant NO_FILE are given in the private part (the declarations at the end of the package, between the reserved words private and end). A package body for the above package is sketched below:
package body SIMPLE_IO is type FILE_DESCRIPTOR is record ... end record; DIRECTORY : array (FILE_NAME) of FILE_DESCRIPTOR; ... procedure CREATE (FILE : out FILE_NAME; NAME : in STRING) is ... end; procedure READ (ITEM : out INTEGER; FILE : in FILE_NAME) is ... end ; procedure WRITE (ITEM : in INTEGER; FILE : in FILE_NAME) is ... end; begin -- initialization of DIRECTORY and other local objects end SIMPLE_IO;
Within the body, file names are integers indexing an internal directory which is declared as an array. However, an external user of the package cannot use this internal information: for example, an external user cannot perform arithmetic on file names, since the arithmetic operators for the type FILE_NAME can be used only inside the package.
With the above definition of the type FILE_NAME, it remains possible for users to assign file names, and also to compare file names for equality and inequality. For the following variation of the previous package, even these operations are denied:
package SAFE_IO is type FILE_NAME is limited private; procedure CREATE (FILE : in out FILE_NAME; NAME : in STRING); procedure CLOSE (FILE : in out FILE_NAME); procedure READ (ITEM : out INTEGER; FILE : in FILE_NAME); procedure WRITE (ITEM : in INTEGER; FILE : in FILE_NAME); FILE_ERROR : exception; private type FILE_INDEX is range 0 .. 50; NOT_CREATED : constant FILE_INDEX := 0; type FILE_NAME is record INDEX : FILE_INDEX := NOT_CREATED; -- default value end record; end SAFE_IO;
Even the operations of assignment and equality comparison are not available for a limited private type. Therefore, the user of package SAFE_IO can only:
procedure TRANSFER_ITEM(SOURCE, DESTINATION : in FILE_NAME) is ITEM : INTEGER; begin READ (ITEM, SOURCE); WRITE (ITEM, DESTINATION); end;
Since neither assignment nor comparison of file names is possible, defining a constant NO_FILE would not be very useful in this formulation. The only safe way to ensure that files are always initialized is to provide a default value, as we have done in the full declaration of FILE_NAME. This allows the package body to control the consistency of all operations: CREATE can check that the file has not already been created; READ and WRITE can check that the file has been created; and CLOSE can reset the internal value to NOT_CREATED. The exception FILE_ERROR can be raised by the body if any of these checks fails. Note that in this variation of the package, the file parameter mode for CREATE has been changed to in out, in order to allow this procedure to check whether the file has already been created, and to avoid overwriting an existing file name.
The prohibition of assignment, in this formulation, is quite essential if we want the package body to be in full control of active files. Let us assume, for example, that the package body maintains a count of active files as the difference between the number of (correct) calls of CREATE and the number of (correct) calls of CLOSE. If assignment were allowed, it would be possible to call CLOSE twice for the same file value (having copied this value into another variable), and this count would then not be reliable.
For the more classical examples of encapsulated data types (from the current literature), the reader is referred to chapter 12 of this document (a generic definition of the type queue) and to section 12.4 of the Reference Manual (a generic definition of the type stack).