17 Idioms


Overview

Using a language effectively requires more than a knowledge of it's syntax and semantics. An acquaintaince with common problems, and the various methods to solve these problems can simplify the process of writing programs, and in this respect Ada is no exception. This chapter is involved in exposing some of the more common idioms of programming in Ada.


Introduction

Abstraction is one of the most important part of programming, and it should always be considered when programming solutions. You must always make an effort to distinguish what service a type is providing and how it is implemented. You expose the what, but try to hide the how.

Similarly a knowledge of how to hide things in Ada is important. Ada provides for (predominantly) orthogonal concepts of type/behaviour, and modularity/controlling visibility. The semantics of a program (how it runs, what it does) are independent of whether you design the program carefully for modularity and robustness in the face of change. Clearly though it is more often than not worth the effort to design a system competently.


Abstraction

Abstractions allow us to present only that which we want the user to be concerned with, and not giving access to information which is irrelavent. For example in the all too often used stack example, the user should not be too concerned with whether the implementation uses arrays or linked lists.

A rough stab at an abstraction could be...

Here however the details of the implementation are rather obvious; the how tends to get in the way of the what. This can be changed simply by moving whatever is not relavent into the private section...

Although the programmer who uses your abstraction can see all the details if they have access to the code, they can't write their programs to rely on this information. Also the separation clearly enforces the notion of what is important for the abstaction, and what is not.

Alternatively we could change the private section to reflect a linked list implementation...

A user program could then use either package as follows...

The concept of being able to substitute a different implementation, or more precisely, only relying on the public interface, is a very important design principle for building robust systems.

(Note that these two implementations aren't quite identical. Consider the case where we have

An array implementation will work correctly when copied, but a linked list implementation will only copy the head pointer. Both a and b will then point to the same linked list. The solution to this is presented later on. For the examples presented here on, assume that this problem has been fixed).


Creating abstractions from other abstractions (code reuse)


Private inheritance

It is important to establish the difference between what services a type provides, and how it implements that service. For example a list abstraction (that has appropriate routines, and itself may be implemented as a linked list, or an array) can be used to implement a queue abstraction. The queue abstraction will have only a few operations:

We want to ensure there is a clear distinction between the abstraction and the implementation in our program. Preferably the compiler should check and ensure that no-one makes the mistake of calling routines from the implementation, rather than the abstraction.

Below is an example package which can lead to problems.

Here type queue inherits all of the operations of the list type, even those that aren't appropriate, such as remove_from_tail. With this implementation of queue, clients of the abstraction could easily break the queue, which should only allow insertion at the tail, and removal from the head of the list.

For example a client of the queues package (something that "with queues"), could easily do the following

What we need to do is advertise a different abstraction, but reuse the list abstraction privately.


Another example

Let's say we wish to create a stack package, but we don't want to recode all of the routines. We may already have a linked list implementation that could form the basis of the storage for the stack. The list package stores integers.

Assume the package lists is defined as follows...

We can then make use of Ada's ability to hide the full view of a type in the private section. Here we declare a brand new type (as far as the client is concerned), which is actually implemented as a derived type. The client can't "see" this - the "under the hood" details remain well hidden.

We implement the stack as...

We now have to relate the implicity declared routines to those that are advertised publically. This is done by a simple "call through" mechanism.

This is ok for publically advertised routines that have different names from the implicity declared routines (those inhertied from type list).

However in the package specification there are two functions full which have the profile

One is explicity declared in the public section, one implicity declared in the private section. What's going on? The first function specification in the public part of the package is only a promise of things to come. It is an as-yet unrealised function. It will be completed in the package body. As well we have another (implicity) declared function which just happens to have the same name and profile. You still have to describe to the Ada compiler how you are going to implement the function declared in the public part of the package. The Ada compiler can see that both functions have the same name and profile, but it does not assume that public function should be implemented by the private function.

For example imagine that integer_lists.full always return false, to indicate that the linked list was never full, and could always grow. As the implementor of the stack package you may have decided that you wanted to enforce a limit on the stack, so that it would only contain 100 elements at most. You would then write the function stacks.full accordingly. It would be incorrect for the implementation of function full to default back to the linked list implementation.


So far so good. How do I write the function full?

Because of the visibility rules of Ada, the explicit public declaration completely hides the implicity declared private one, and so is not accessable at all. The only solution is to call on the function in the list package, which you have to do explicity.

This seems all terribly obtuse and annoying. Why does Ada force this upon you? The reason you are encountering this sort of problem is because of the nature of constructing programs with independent name spaces (different scopes where identical names do not clash with each other). Ada is merely providing a solution to a problem that is an inevitable consequence of name spaces. The alternative, of having to work with a language in which there can be no names the same is worse than this solution. So don't shoot the messenger just because you don't like the message :-).

This is a very important point to note. Don't go on unless you understand this point.

The full package body for stacks would be


Creating abstractions by using generic abstractions

Often a generic package that implements a suitable data structure can be used to implement another abstraction. This is very similar to the section presented previously.

Assume the package generic_lists is defined as...

We can instantiate this generic to create a new package that will store the integers for us. The question is "where do we want to instantiate it, such that it won't get in the way of the abstraction, or cause other developement problems?". The solution to this (as it is to a lot of these type of questions) is in the private section of the package.

We can then use the generic as follows...

The package body is the same as previously described.

Here we can enforce privacy, without changing what the type does for a client.


Abstractions from composition

Abstractions can be implemented by using composition (composing new abstractions by putting existing abstractions together, typically in a record) and by forwarding subprogram calls onto the appropriate component object. (This is all pretty simple stuff!).

In this example calls on a queue are "forwarded" onto the list component, which is responsible for implementing some aspect of the queue abstraction (in this case a major part!).


Abstracting common functionality - families of abstractions

Both implementations of the stack described above provided a common set of routines, that is push, pop etc. In Ada we can state the commonalities using a common type. The common type will describe what routines are to be provided, and their profiles, but not actually describe how to implement them. Type derived from this type are forced to implement each subprogram in its own way. This is called an abstract type. It is an abstraction abstraction!

The reason we go to all of this trouble is to tell the clients of the stack that no matter what implementation they choose, they can be sure of getting at least the functionality described by the abstract type.

Here we have made the type abstract - this package just provides a series of subprogram that must be implemented by someone else. We could also have made the type private...

Either in the same package (or more typically in another package) we can extend the type, and create a real type that implements the abstract type...

As described in the section on composition, you will have to forward calls onto the linked list within the unbounded_stack. The package body would therefore be...


Packaging

In Ada, types and packages are almost completely independent of each other. This allows you to devise a solution to a problem in terms of types and operations, and later decide on how these parts should be split into pieces to meet the software engineering requirements of ease of modification, encapsulation, modularisation, reduced recompilation costs, namespace management etc.

This has been demonstrated already, in the choice to hide implementation details in the various packages above. However Ada also provides further choices that can be made in this regard.


Namespace management

In language such as C, there is only one namespace. There is no hierachy of names; no ability to 'hide' or qualify a name. A good analogy is the first version of the Mac OS, which had no directories. All filenames had to be different, and could not conflict with predefined system filenames. The solution typically adopted in C to resolve this problem is to prepend some form of semantic information in the name. For example all posix thread routines are prepended with 'pthread_'.

Ada83 solved this problem by allowing packages which contain their own namespace, in which locally defined names don't conflict with other names (the analogy is for one level of subdirectories (actually not quite accurate, as a package can contain other entities...)).

Ada95 however expands the namespace concept with the inclusion of child packages to allow for truly hierachic namespaces. Child packages are typically used to model some form of hierachy present in the problem domain. For example a unix binding may implement packages

Alternatively the package hierachy can be used to reflect the inhertiance hierachy of an object oriented type. Note however the package hierachy does not define the inhertiance hierachy - the type declarations do that.

The example given earlier of an abstract stack, and a concrete implementation of an unbounded stack is a good example of how we could have chosen a different organisation, without affecting the meaning of the abstraction.

We could create another type, bounded_stack, and decide to put it into the package stacks.bounded.


Handling dynamic structures properly

As discussed earlier in the implementation of a stack with a linked list, making a copy of such a type through an assignment statement doesn't quite work. The problem is that copying often only copies a pointer to the head of a list (or tree) and does not copy the entire list. Another problem is that the space allocated to a linked list is often not recovered when an object goes out of scope.

An example which highlights these problems is...

In Ada95 you can solve these problems by providing special routines that are "automagically" called when a values are being copied, when they go out of scope, or when they are initialized. For this to happen, the object in question must be derived from a special type called Controlled, defined in package Ada.Finalization.

The three routines automagically called are called Initialize, Adjust and Finalize. They are called in the following contexts...

Here we finally give the full details for package List. The code for it looks like...

Note that the routines were declared in the private section. This prevents clients from calling them when it is inappropriate.

The package body would be implemented as follows...

The use of controlled types gives a good degree of control over dynamic objects. It simplifies the life of a client of the type as it relieves them of the worry of having to manage the types memory. It also allows for easy substitution of dynamic and non dynamic solutions to problems.

Typically once the 3 routines (initialize, adjust, finalize) have been written, the rest of the services can be written without regard to these features.


Enforcing initialization

In C++ you can enforce the initialization of all objects by supplying a class with a constructor, which is called when an object is created. The same effect in Ada is achieved by assigning a value the result of calling a function.

For example

In this example the object a is properly initialized to represent an empty stack. You can only use functions provided in the package; you can't set it any other way, because the type is private. However as can be seen, the object b has not been initialized. The language has not enforced an initialization requirement.

The only way to do this is through the use of record discriminants. The partial view of the type is declared to have discriminants, even if the full view does not. As the number of fields that a record has may depend on a discriminant, and because Ada likes constrained objects (one's whose size is known) the combination of discriminant and private type causes the compiler to enforce all objects of the type to be initialized.

This is all quite non intuitive, and rather counter to the Ada notion of readability (IMHO). Certainly C++ manages this is a more programmer friendly way.


Mutually Recursive types

Sometimes there is a need for two (or more) types that are mutually recursive, that is they have pointers that point at each other. An example is a doctor and a patient, who wish to maintain pointers at each other. Unless you wish to place them together in the same package, Ada does not properly support you. The best you can do if you want to retain separate compilation, is to define two base types, then join them together later in other packages.

If you can forsee all operations required, or at least those required by each other, you can place them in package doctors_and_patients. Futher operations can be added along with subsequent type declarations (for example type doc).

It's probably worth point out here that package bodies can see into package specs, so...


to the index...