Ada Programming/Tips
Full declaration of a type can be deferred to the unit's body
[edit | edit source]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:
package
Private_And_Bodyis
type
Private_Typeis
limited
private
; -- Operations...private
type
Body_Type; -- incomplete type declaration completed in the bodytype
Private_Typeis
access
Body_Type;end
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
[edit | edit source]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.
generic
type
Elementis
private
;package
Setsis
type
Setis
private
; [..]generic
with
procedure
Apply_To_One (The_Element :in
out
Element);procedure
Apply_To_All (The_Set :in
out
Set);end
Sets;
For a view of Functional Programming in Ada see.[1]
Compiler Messages
[edit | edit source]Different compilers can diagnose different things differently, or the same thing using different messages, etc.. Having two compilers at hand can be useful.
selected component
- 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 toFoo
, while the other refers toBar
. 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 theFoo.Bar
example these correspond toFoo
, '.
', andBar
. 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,
with
Pak;package
Foois
type
Tis
new
Pak.Bar; -- Oops, Pak is generic!end
Foo;
- the compiler may print a diagnostic message about a prefixed component:
Foo
's author thought thatPak
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
[edit | edit source]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.
Very_Big: 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 calculation 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 not
A'LengthA'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
type
Ran_6is
range
1 .. 6;type
Mod_6is
mod
6;
and then
-- 1if
A'Lengthin
Ran_6then
-- OK … -- 2if
not
A'Lengthin
Ran_6then
-- not OK … -- this is the same asif
(not
A'Length)in
Ran_6then
-- not OK … -- 3if
A'Lengthin
1 .. 6then
-- OK … -- 4if
not
A'Lengthin
1 .. 6then
-- not OK … -- 5if
A'Lengthin
Mod_6then
-- OK? … -- 6if
not
A'Lengthin
Mod_6then
-- OK? …
- The second conditional cannot be compiled because the expressions to the left of
is incompatible to the type at the right. Note thatin
has precedence overnot
. It does not negate the entire membership test but onlyin
A'Length
.
- The fourth conditional fails in various ways.
- The sixth conditional might be fine because
turnsnot
A'Length
into a modular value which is OK if the value is covered by modular typeMod_6
.
- 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),not
in
if
A'Lengthnot
in
Ran_6then
-- OK …
- See
I/O
[edit | edit source]Text_IO Issues
[edit | edit source]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:
declare
The_Line: String(1..100); Last: Natural;begin
loop
Text_IO.Get_Line(The_Line, Last); -- do something with The_Line ...end
loop
;exception
when
Text_IO.End_Error =>null
;end
;
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).
Quirks
[edit | edit source]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.[2] As a provisional fix, it is suggested to insert
delay
0.0;
before any use of Real_Time
services.
Stack Size
[edit | edit source]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.
References
[edit | edit source]- ↑ Functional Programming in...Ada?, by Chris Okasaki
- ↑ Vincent Celier (2010-03-08). "Timing code blocks". comp.lang.ada. (Web link). Retrieved on 2010-03-11. "The problem is now understood and corrected in the development version of GNAT." Usenet article forwards this information from AdaCore.