In this chapter, we discuss the advantages of strong typing and how it can be used to avoid common implementation and maintenance issues.
In this section, we discuss an application that accesses a two-dimensional table. We first look into a typical implementation, and then discuss how to improve it with better use of strong typing.
Let's look at an application that declares a two-dimensional lookup table, retrieves a value from it an displays this value.
In this application, we use
Y as indices to access the
Tab table. We store the value in
V and display it.
In principle, there is nothing wrong with this implementation. Also, we're
already making use of strong typing here, since accessing an invalid
position of the array (say
Tab (6, 25)) raises an exception.
However, in this application, we're assuming that
X always refers
to the first dimension, while
Y refers to the second dimension.
What happens, however, if we write
Tab (Y, X)? In the application
above, this would still work because
Tab (5, 1) is in the table's
range. Even though this works fine here, it's not the expected behavior.
In the next section, we'll look into strategies to make better use of
strong typing to avoid this problem.
One could argue that the problem we've just described doesn't happen to competent developers, who are expected to be careful. While this might be true for the simple application we're discussing here, complex systems can be much more complicated to understand: they might include multiple tables and multiple indices for example. In this case, even competent developers might make use of wrong indices to access tables. Fortunately, Ada provides means to avoid this problem.
Using stronger typing¶
In the example above, we make use of the
Positive type, which is
already a constrained type: we're avoiding accessing the
using an index with negative values or zero. But we still may use indices
that are out-of-range in the positive range, or switch the indices, as in
Tab (Y, X) example we mentioned previously. These problems can
be avoided by defining range types for each dimension. This is the updated
Now, we not only avoid mistakes like
Tab (Y, X), but we also detect
them at compile time! This might decrease development time, since we don't
need to run the application in order to check for those issues.
Also, maintenance becomes easier as well. Because we're explicitly stating
the allowed ranges for
Y, developers can know how to
avoid constraint issues when accessing the
Tab table. We're also
formally indicating the expected behavior. For example, because we declare
X to be of
X_Range type, and that type is used in the first
Tab, we're documenting --- using the syntax of the Ada
language --- that
X is supposed to be used to access the first
Tab. Based on this information, developers that need
to maintain this application can immediately identify the purpose of
X and use the variable accordingly.
In this section, we discuss another example where the use of strong typing is relevant. Let's consider an application with the following requirements:
- The application receives the transmission of chunks of information.
- Each chunk contains two floating-point coefficients.
- Also, these chunks are received out of order, so that the chunk itself includes an index indicating its position in an ordered array.
- The application also receives a list of indices for the ordered array of chunks. This list --- a so-called selector --- is used to select two chunks from the array of ordered chunks.
- Due to external constraints, the application shall use the unordered
array; creating an array of ordered chunks shall be avoided.
- A function that returns an ordered array of chunks shall be available for testing purposes only.
- A function that returns the selected chunks shall be available for testing purposes only.
- A function that returns a mapping from the index of ordered chunks to the index of unordered chunks must be available.
For example, consider the following picture containing input chunks and a selector:
By using the mapping, we can select the correct chunks from the input (unordered) chunks. Also, we may create an array of ordered chunks for testing purposes.
Let's skip the discussion whether the design used in this application is good or not and assume that all requirements listed above are set on stone and can't be changed.
This is a typical specification of the main package:
And this is a typical specification of the
Test child package:
This is the corresponding body of the main package:
This is the corresponding body of the
Test child package:
Note that the information transmitted to the application might be
inconsistent due to errors in the transmission channel. For example, the
Chunk record) might be wrong. In a
real-world application, we should deal with those transmission errors.
However, for the discussion in this section, these problems are not
crucial, so that we can simplify the implementation by skipping error
Let's finally look at a test application that makes use of the package we've just implemented. In order to simplify the discussion, we'll initialize the array containing the unordered chunks and the selector directly in the application instead of receiving input data from an external source.
In this line of the test application, we retrieve the chunk using the index from the selector:
C1 : Chunk := C (M (S (I)));
C contains the unordered chunks and the index from
refers to the ordered chunks, we need to map between the ordered index
and the unordered index. This is achieved by the mapping stored in
If we'd use the ordered array of chunks, we could use the index from
S directly, as illustrated in the following function:
In this relatively simple application, we're already dealing with 3 indices:
- The index of the unordered chunks.
- The index of the ordered chunks.
- The index of the selector array.
The use of the wrong index to access an array can be a common source of issues. This becomes even more problematic when the application is extended and new features are implemented: the amount of arrays might increase and developers need to be especially careful not to use the wrong index.
For example, a mistake that developers can make when using the package
above is to skip the mapping and access the array of unordered chunks
directly with the index from the selector --- i.e.
C (S (I)) in the
test application above. Detecting this mistake requires extensive testing
and debugging, since both the array of unordered chunks and the array of
ordered chunks have the same range, so the corresponding indices can be
used interchangeably without raising constraint exceptions, even though
the behavior is not correct. Fortunately, we can use Ada's strong typing
to detect such issues in an early stage of the development.
Using stronger typing¶
In the previous implementation, we basically used the
for all indices. We can, however, declare individual types for each index
of the application. This is the updated specification of the main package:
By declaring these new types, we can avoid that the wrong index is used. Moreover, we're documenting --- using the syntax provided by the language --- which index is expected in each array or function from the package. This allows for better understanding of the package specification and makes maintenance easier, as well as it helps when implementing new features for the package.
This is the updated specification of the
Test child package:
Note that we also declared a separate type for the array of ordered
Ord_Chunks. This is needed because the arrays uses a
different index (
Ord_Chunk_Index) and therefore can't be the same
Chunks. For the same reason, we declared a separate type
for the array of selected chunks:
As a side note, we're now able to include a
Ord_Chunks that verifies that the index stored in the each chunk
matches the corresponding index of its position in the ordered array.
We also had to add a new private package that includes a function that
retrieves the range of an array of
Chunk type --- which are of
Chunk_Index type --- and converts the range using the
This is needed for example in the
Get_Mapping function, which has
to deal with indices of these two types. Although this makes the code a
little bit more verbose, it helps documenting the expected types in that
This is the corresponding update to the body of the main package:
This is the corresponding update to the body of the
This is the updated test application:
Apart from minor changes, the test application is basically still the same. However, if we now change the following line:
C1 : Chunk := C (M (S (I)));
C1 : Chunk := C (S (I));
The compiler will gives us an error, telling us that it expected the
Chunk_Index type, but found the
By using Ada's strong typing, we're detecting issues at compile time
instead of having to rely on extensive testing and debugging to detect
them. Basically, this eliminates a whole category of potential bugs
and reduces development time. At the same time, we're improving the
documentation of the source-code and facilitating further improvements
to the application.