Ada Programming/Tips

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Ada. Time-tested, safe and secure.
Ada. Time-tested, safe and secure.

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_Body is

  type Private_Type is limited private;

  -- Operations...

private
  type Body_Type;   -- incomplete type declaration completed in the body
  type Private_Type is 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 Element is private;
package Sets is
  type Set is 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 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.Bar example these correspond to Foo, '.', and 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,
with Pak;
package Foo is
   type T is new Pak.Bar;  --  Oops, Pak is generic!
end Foo;
the compiler may print a diagnostic message about a prefixed component: Foo's author thought that Pak 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 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 not A'Length 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

   type Ran_6 is range 1 .. 6;
   type Mod_6 is mod 6;

and then

   --  1
   if A'Length in Ran_6 then  --  OK--  2
   if not A'Length in Ran_6 then  --  not OK--  this is the same as
   if (not A'Length) in Ran_6 then  --  not OK--  3
   if A'Length in 1 .. 6 then  --  OK--  4
   if not A'Length in 1 .. 6 then  --  not OK--  5
   if A'Length in Mod_6 then  --  OK?--  6
   if not A'Length in Mod_6 then  --  OK?
The second conditional cannot be compiled because the expressions to the left of in is incompatible to the type at the right. Note that not has precedence over in. It does not negate the entire membership test but only A'Length.
The fourth conditional fails in various ways.
The sixth conditional might be fine because not turns A'Length into a modular value which is OK if the value is covered by modular type Mod_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 not in for the membership test (which, btw., is the language-intended form),
   if A'Length not in Ran_6 then  --  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]

  1. Functional Programming in...Ada?, by Chris Okasaki
  2. 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.

See also[edit | edit source]

Wikibook[edit | edit source]

Ada Reference Manual[edit | edit source]