Pages

Tuesday, September 4, 2012

AdaTutor - Subprograms and Packages (3)

Information Hiding: Private Types

Information hiding has nothing to do with secrecy; it means containing certain
programming details to a package so that the parts of the program outside the
package can't depend on them.&nbp; Thus, when these details change, other parts of
the program aren't affected.

Let's write the specification (only) for a graphics CRT controller package
without information hiding. Then we'll improve on it by using a private type.

The package will let us create arrays in memory representing screens. We can
create as many of these "virtual screens" as we like, and draw dots, lines, and
boxes on them in memory. We can also display any virtual screen. Here's the
package specification:

This package assumes that the resolution of the CRT is 480 vertically by 640
horizontally. A screen is represented in memory by a two-dimensional array,
each element of which contains one of six possible colors. Procedures to draw
other figures (circles, etc.) could have been added to the package.

That package specification makes the calling program very clear! For example,

The first executable line clears memory for screen S1 to all black, because of
the default parameter in the specification of Clear. The next line clears S2
to all white. The next three lines draw a line and a dot on S1 and display S1.
The program then draws a box and a dot on S2 and displays S2. Named notation
could optionally have been used for the first parameter of each call, and for
the objects of type Point, e.g., (Row => 240, Column => 160).

Question

At a sacrifice in clarity (not recommended!), the calling program CRT_Demo
could draw a dot in the center of screen S1 without calling Dot. Which of the
following statements would accomplish the same thing as

You're right! At a sacrifice in clarity, the appropriate element of S1 can be
set directly to Red to draw a dot in the center of the screen. Number 2 is
wrong because the names Row and Column apply to type Point, not to subscripts
of the array.

No, the names Row and Column apply to type Point, not to subscripts of the
array. For this reason number 2 won't compile.

Although the calling program is very clear, there's one disadvantage that can
be eliminated by using a private type: if the resolution of the screen is
changed, the calling program is affected. For example, the two calls to Dot
are each intended to draw a dot in the center of the screen. These calls will
have to be changed when the resolution changes, or the dot will no longer be in
the center. Also, nothing prevents CRT_Demo from saying
S1(Row => 240, Column => 320) := Red; directly, instead of calling Dot.
This is a disadvantage, because referencing
the elements of the array directly makes CRT_Demo very susceptible to changes
in the package.

It would be better to force CRT_Demo to call our procedure Dot, and make it
impossible for CRT_Demo to use the fact the a screen is represented by a
two-dimensional array. Then the representation of a screen can change without
affecting the correctness of CRT_Demo.

We'll improve our package specification and calling program to make Screen_Type
a private type, so that its details can be used only inside the package. Since
the screen resolution won't be available outside the package, we'll normalize
Row and Column, making them floating point numbers in the range 0.0 .. 1.0.

Outside the package, there are only four things a calling program may do with
objects of a private type like Screen_Type:

Create them: S1, S2 : Screen_Type;

Assign them: S1 := S2;

Test them for equality and inequality: if S1 = S2 ... if S1 /= S2 ...

Use any procedures, functions, and infix operators provided by the package:
Clear(S1); Display(S2); etc.

Note that the calling program, outside the package, can no longer say
S1(240, 320) := Red;, because that's not one of the four things listed above.
The calling program is forced to call Dot. The information that Screen_Type is
an array can be used by code only inside the package. Of course, the compiler
uses this information when compiling code outside or inside the package.
That's why the definition of Screen_Type must be in the package specification,
not the body. But the programmer isn't allowed to use this information outside
the package.

Inside the package there are no such restrictions. To write the bodies of the
subprograms, we'll have to use the structure of Screen_Type and write
statements similar to S1(240, 320) := Red;.

Now, if a change is made only to the private part of the package specification,
CRT_Demo won't have to be revised. It will have to be recompiled, because the
Ada compiler needs the information in the private part of the package
specification. (The linker won't link with obsolete units, but will tell us
what we must recompile.) Also, the package body may have to be rewritten. But
if CRT_Demo was correct before, it will remain correct without any revision!
The effects of the change are confined to the package. Containing the effects
of changes by means of information hiding is one of Ada's greatest features.

Question

True or False? If the private part of a package specification is changed, the
calling program must be recompiled before the package body is recompiled.

False. The package body and the calling program don't depend on each other,
but only on the package specification. Thus they can be compiled in either
order after the package specification is compiled.

You're right! The package body and the calling program don't depend on each other,
but only on the package specification. Thus they can be compiled in either
order after the package specification is compiled.

If a package exports a constant of a private type, the constant is declared in
the "public" part of the package specification, but it can't be assigned a
value until we get to the private part. This is called a deferred constant.
For example,

CRT_Demo could then say S1 := Flag_Of_Italy; or Display(Flag_Of_Italy);.

Type Text and Limited Private Types

Earlier we remarked that Ada strings are of fixed length. Ada 95 comes with
several string handling packages not found in Ada 83. All of them are
described in
Annex A.4 of the Ada 95 LRM. The most important of these are

Fixed-Length String Handling:

Ada.Strings.Fixed

Bounded-Length String Handling:

Ada.Strings.Bounded

Unbounded-Length String Handling:

Ada.Strings.Unbounded

Since Ada 83 doesn't have these packages, section 7.6 of the Ada 83 RM
suggested that we create a type Text to get around the fact that Ada strings
have fixed length. Instead of declaring objects to be Strings, we'll declare
them to be of type Text, which will simulate variable-length strings. Then
we'll see how this creates a need for limited private types.

The package specification in section 7.6 of the Ada 83 RM makes use of
discriminated records. Since we haven't yet covered that topic, we'll give a
simplified presentation here. Our type Text corresponds closely to the type
Bounded_String in the Ada 95 package Ada.Strings.Bounded.

Any appropriate maximum length may be used in place of 80. It isn't necessary
to initialize the Val field to all blanks, because the Len field keeps track of
how much of the Val field is significant. The Len field is given a default
value of 0, so that objects of type Text will be initialized to zero length.

For example, we could declare T1 : Text;. We could then set T1.Len := 5; and
T1.Val(1 .. 5) := "Hello";. The fact that the last 75 characters of T1.Val
might contain garbage is of no consequence, because T1.Len tells our program
to consider only the first 5 characters of T1.Val. For example, if our program
withs and uses Ada.Text_IO, it might say Put_Line(T1.Val(1 .. T1.Len));. Since
T1.Len is a variable, we've simulated variable-length strings.

A minor disadvantage is that, for each object of type Text, we reserve enough
memory for the longest possible length (80 in this example). Discriminated
records, to be covered in the section on More Records and Types, can overcome
this disadvantage.

Type Text will be much easier to use if we write some subprograms. First, we
need to convert between types Text and String. Conversion in both directions
is very simple:

Now we can write, for example, T1 : Text := Txt("Hello"); and we don't even
have to count the characters of "Hello". Later, the program might execute
T1 := Txt("Type Text is very convenient."); showing that T1 simulates a
variable-length string. If we with and use Ada.Text_IO in our program, we can
write Put_Line(Str(T1));.

It would be convenient to overload the & operator to concatenate two Texts, or
a Text with a String. We can also overload four of the relational operators:

Question

True or False? A call to Txt with the null string, Txt(""), will raise a
Constraint_Error.

False. The function will work correctly even for the null string. The first
executable statement will set Answer.Len to 0, and the second executable
statement will do nothing. The check for Constraint_Error is suppressed when a
null slice is involved.

You're right! The first executable statement will set Answer.Len to 0, and
the second executable statement will do nothing. The check for
Constraint_Error is suppressed when a null slice is involved. Thus the
function will work correctly even for the null string.

There are two problems with type Text. The way Ada assigns arrays is less than
ideal when assigning objects of type Text, and the way Ada tests for equality
is totally unacceptable. Suppose we have T1, T2 : Text; and then we execute
T1 := Txt("Hello"); and then T2 := T1;. In doing the assignment, Ada will
copy all 80 characters of T1.Val, even though the last 75 characters contain
garbage and only the first 5 characters (and the Len field) need be copied.
Perhaps we could live with this inefficiency, but Ada's test for equality of
arrays creates a more serious problem.

If we write T1 := Txt("aaaaaaaaaa"); and T2 := Txt("bbbbbbbbbb"); and then
T1 := Txt("Hello"); and T2 := Txt("Hello");, Ada will say that T1 /= T2,
because it compares the entire Val fields: T1.Val(6 .. 10) is "aaaaa", but
T2.Val(6 . .10) is "bbbbb". We want Ada to compare only the first 5 characters
of T1 and T2, as both Len fields are 5. We could modify function Txt (that
converts from String to Text) to pad the rest of the Val field with blanks, so
that the test for equality would be correct, but that would reduce efficiency.

We could also try to get around this problem by writing our own function Equal:

This function would work, but we might forget to write if Equal(T1, T2) and
write if T1 = T2 instead. Similarly, we could write a procedure to assign
Texts efficiently, and forget to use it and write T2 := T1;.

This gets around the problem that we might forget to write if Equal(T1, T2) and
write "if T1 = T2" instead. However, it doesn't solve the problem of assigning
Texts efficiently. For this we need a limited private type.

Ada will prevent us from assigning Texts and testing them for equality if we
create a package and make type Text limited private. Outside the package there
are only two things we may do with objects of a limited private type:

Create them: T1, T2 : Text;

Use any procedures, functions, and infix operators provided by the package:
T1 & T2 etc.

We can't test for equality and inequality unless the package includes a
function "=". Also, Ada won't let us write T2 := T1;, but the package could
provide a procedure to assign Texts. Here's our package specification:

In summary, we used limited private for type Text because Ada's definitions of
equality and assignment weren't appropriate for that type, and we wanted to
provide our own definitions. However, Ada's definitions were appropriate for
Screen_Type, so we made that a private type to contain the effects of changes.

In Ada 95, the choice between private and limited private should depend on
whether we need to redefine assignment, not equality, because Ada 95 lets us
redefine "=" even if the type isn't limited private.

Suppose we want to write a package that lets us create objects of type Stack,
and Push and Pop integers on them. The stacks will be last in, first out.
(For now, ignore the possibilities of stack underflow and overflow.) Should
type Stack be private or limited private?

No, type Stack is similar to type Text in that only part of the array (the part
up through Sp - 1) is significant; the rest contains garbage. Thus, if we
wanted to assign Stacks, we would want to copy only the significant part of the
array, not the whole array. Also, Ada's built-in test for equality of Stacks
is unsatisfactory for the same reason as for Texts. Therefore, type Stack
should be limited private.

You're right! Similar to type Text, only part of the array in type Stack (the
part up through Sp - 1) is significant; the rest contains garbage. Thus, if we
wanted to assign Stacks, we would want to copy only the significant part of the
array, not the whole array. Also, Ada's built-in test for equality of Stacks
is unsatisfactory for the same reason as for Texts. Therefore, type Stack
should be limited private.

Hierarchical Libraries

Earlier we considered a package CRT_Controller that had procedures to draw
dots, lines, and boxes. Suppose that we have compiled that package and several
calling programs, and then we wish to add a procedure to draw a circle. If we
add the procedure directly to CRT_Controller, we'll have to recompile
CRT_Controller, and, as a result, all of the previously written calling
programs, because these with the package. This is true even though the
previously written calling programs don't call the new procedure to draw a
circle.

In Ada 95, we can write a child package to get around this problem. Its name
is CRT_Controller followed by a dot and another name, for example,
CRT_Controller.More. (The pre-supplied package Ada.Text_IO is a child of the
package Ada; that's why its name contains a dot.) A child package must be
compiled after the specification of the parent, such as CRT_Controller. We do
not change or recompile CRT_Controller. Instead, we put our new routines, such
as Circle, in CRT_Controller.More and compile it.

Now the previously written calling programs that with CRT_Controller don't have
to be recompiled. The new programs, that can call Circle (as well as Dot,
Line, Box, etc.) should say with CRT_Controller.More;. This automatically does
with CRT_Controller; as well, so we don't need to mention CRT_Controller in a
with statement. However, if we want to use both the parent package and the
child package, we must mention both in a use statement.

A parent package can have several children, and the children can have children,
to any depth. While only packages can have children, the children themselves
can be subprograms (procedures or functions) as well as packages.

Although subprograms can't have "children," they can, of course locally declare
other subprograms, and these declarations may say is separate, even in Ada 83.

A child package can be made private to its parent. Let's suppose that, for
some reason, we wanted the resources of CRT_Controller.More (such as Circle) to
be available only within the CTR_Controller family of packages and subprograms.
We can write

Now units outside this family can't with CRT_Controller.More, because this
package is a private child. Also, the specifications of other children of
CRT_Controller can't with CRT_Controller.More either, because if they could,
they could export its resources outside the family. However, the bodies of
the other children of CRT_Controller can with CRT_Controller.More, because
resources in the bodies aren't available outside the family.