Ada Programming/Tips

Full declaration of a type can be deferred to the unit's body
Often, you'll want to make changes to the internals of a private type. This, in turn, will require the algorithms that act on it to be modified. If the type is completed in the unit specification, it is a pain to edit and recompile both files, even with an IDE, but it's something some programmers learn to live with.

It turns out you don't have to. Nonchalantly mentioned in the ARM, and generally skipped over in tutorials, is the fact that private types can be completed in the unit's body itself, making them much closer to the relevant code, and saving a recompile of the specification, as well as every unit depending on it. This may seem like a small thing, and, for small projects, it is. However, if you have one of those uncooperative types that requires dozens of tweaks, or if your dependence graph has much depth, the time and annoyance saved add up quickly.

Also, this construction is very useful when coding a shared library, because it permits to change the implementation of the type while still providing a compatible ABI.

Code sample:

Private_And_Body Private_Type  ; Body_Type; Private_Type  Body_Type; Private_And_Body;

The type in the public part is an access to the hidden type. This has the drawback that memory management has to be provided by the package implementation. That is the reason why Private_Type is a limited type, the client will not be allowed to copy the access values, in order to prevent dangling references.

Normally, the full type definition has to be given in the specification because the compiler has to know how much place to allocate to objects in order to produce code using this type. And the astute reader will note that also in this case the full type definition is given for Private_Type: it is an access to some other (albeit incomplete) type, and the size of an access value is known. This is why the full type definition of Body_Type can be moved to the body.

The construction centering around Private_Type is sometimes called an opaque pointer.

Lambda calculus through generics
Suppose you've decided to roll your own set type. You can add things to it, remove things from it, and you want to let a user apply some arbitrary function to all of its members. But the scoping rules seem to conspire against you, forcing nearly everything to be global.

The mental stumbling block is that most examples given of generics are packages, and the Set package is already generic. In this case, the solution is to make the Apply_To_All procedure generic as well; that is, to nest the generics. Generic procedures inside packages exist in a strange scoping limbo, where anything in scope at the instantiation can be used by the instantiation, and anything normally in scope at the formal can be accessed by the formal. The end result is that the relevant scoping roadblocks no longer apply. It isn't the full lambda calculus, just one of the most useful parts.

Element ; Sets Set ; [..]      Apply_To_One (The_Element :   Element); Apply_To_All (The_Set :  Set); Sets;

For a view of Functional Programming in Ada see.

Compiler Messages
Different compilers can diagnose different things differently, or the same thing using different messages, etc.. Having two compilers at hand can be useful.


 * : When a source program contains a construct such as, you may see messages saying something like «selected component "Bar"» or maybe like «selected component "Foo"». The phrases may seem confusing, because one refers to  , while the other refers to  . But they are both right. The reason is that selected_component is an item from Ada's grammar . It denotes all of: a prefix, a dot, and a selector_name. In the   example these correspond to  , ' ', and  . Look for more grammar words in the compiler messages, e.g. «prefix», and associate them with identifiers quoted in the messages.


 * For example, if you submit the following code to the compiler,

Pak Foo T  PakBar Foo


 * the compiler may print a diagnostic message about a prefixed component: 's author thought that   denotes a package, but actually it is the name of a generic package. (Which needs to be instantiated first; and then the instance name is a suitable prefix.)

Universal integers
All integer literals and also some attributes like  are of the anonymous type universal_integer, which comprises the infinite set of mathematical integers. Named numbers are of this type and are evaluated exactly (no overflow except for machine storage limitations), e.g.

Very_Big: := 10**1_000_000 - 1;

Since universal_integer has no operators, its values are converted in this example to root_integer, another anonymous type, the calcuation is performed and the result again converted back in universal_integer.

Generally values of universal_integer are implicitly converted to the appropriate type when used in some expression. So the expression  is fine; the value of   is interpreted as a modular integer since  can only be applied to modular integers (of course a context is needed to decide which modular integer type is meant). This feature can lead to pitfalls. Consider

Ran_6  1  6 Mod_6  6

and then

A Ran_6 …     A  Ran_6 …    ( A)  Ran_6 …    A  1  6 …     A  1  6 …    A  Mod_6 …     A  Mod_6 …


 * The second conditional cannot be compiled because the expressions to the left of  is incompatible to the type at the right. Note that   has precedence over  . It does not negate the entire membership test but only.


 * The fourth conditional fails in various ways.


 * The sixth conditional might be fine because  turns   into a modular value which is OK if the value is covered by modular type.


 * GNAT GPL 2009 gives these diagnoses respectively:

error: incompatible types error: operand of not must be enclosed in parentheses warning: not expression should be parenthesized here


 * A way to avoid these problems is to use  for the membership test (which, btw., is the language-intended form),

A  Ran_6 …


 * See
 * ), and
 * Membership Tests
 * Membership Tests
 * Membership Tests
 * Membership Tests

Text_IO Issues
A canonical method of reading a sequence of lines from a text file uses the standard procedure .Get_Line. When the end of input is reached, Get_Line will fail, and exception End_Error is raised. Some programs will use another function from to prevent this and test for End_of_File. However, this isn't always the best choice, as has been explained for example in a Get_Line news group discussion on comp.lang.ada.

A working solution uses an exception handler instead:

The_Line String1100 Last Natural Text_IOGet_LineThe_Line Last Text_IOEnd_Error

The function End_of_File RM A.10.1(34) works fine as long as the file follows the canonical form of text files presumed by Ada, which is always the case when the file has been written by. This canonical form requires an End_of_Line marker followed by an End_of_Page marker at the very end before the End_of_File.

If the file was produced by another any other means, it will generally not have this canonical form, so a test for End_of_File will fail. That's why the exception End_Error has to be used in those cases (which always works).

Quirks
Using GNAT on Windows, calls to subprograms from might need special attention. (For example, the  function might seem to return values indicating that no time has passed between two invocations when certainly some time has passed.) The cause is reported to be a missing initialization of the run-time support when no other real-time features are present in the program. As a provisional fix, it is suggested to insert

00

before any use of  services.

Stack Size
With some implementations, notably GNAT, knowledge of stack size manipulation will be to your advantage. Executables produced with GNAT tools and standard settings can hit the stack size limit. If so, the operating system might allow setting higher limits. Using GNU/Linux and the Bash command shell, try

$ ulimit -s [some number]

The current value is printed when only  is given to ulimit.

Wikibook

 * Ada Programming
 * Ada Programming/Errors

Ada Reference Manual


|Tips