Full declaration of a type can be deferred to the unit's body edit
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.
private; -- Operations...
typeBody_Type; -- incomplete type declaration completed in the 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 edit
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.
procedureApply_To_One (The_Element :
procedureApply_To_All (The_Set :
For a view of Functional Programming in Ada see.
Compiler Messages edit
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
Foo.Bar, 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
Foo, while the other refers to
Bar. But they are both right. The reason is that selected_component is an item from Ada's grammar (4.1.3 Selected Components (Annotated)). It denotes all of: a prefix, a dot, and a selector_name. In the
Foo.Barexample these correspond to
Bar. 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,
- the compiler may print a diagnostic message about a prefixed component:
Foo's author thought that
Pakdenotes 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 edit
All integer literals and also some attributes like
'Length 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.
constant := 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
A'Length is interpreted as a modular integer since
not 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
then-- OK … -- 2
then-- not OK … -- this is the same as
then-- not OK … -- 3
in1 .. 6
then-- OK … -- 4
in1 .. 6
then-- not OK … -- 5
then-- OK? … -- 6
then-- OK? …
- 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
A'Lengthinto 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),
Text_IO Issues edit
A canonical method of reading a sequence of lines from a text file uses the standard procedure Ada.Text_IO.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 Ada.Text_IO 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:
declareThe_Line: String(1..100); Last: Natural;
loopText_IO.Get_Line(The_Line, Last); -- do something with The_Line ...
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 Ada.Text_IO. 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).
Using GNAT on Windows, calls to subprograms from Ada.Real_Time might need special attention. (For example, the
Real_Time.Clock 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
before any use of
Stack Size edit
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
-s is given to ulimit.