From: M.Kempe@ieee.org (Magnus Kempe)
Newsgroups: comp.lang.ada,comp.answers,news.answers
Distribution: world
Subject: Ada FAQ: Programming with Ada (part [1,2,3,4] of 4)
Followup-To: poster
Reply-To: M.Kempe@ieee.org (Magnus Kempe)
Summary: Ada Programmer's Frequently Asked Questions (and answers),
           part [1,2,3,4] of 4.
         Please read before posting.
Keywords: advanced language, artificial languages, computer software,
          data processing, programming languages, Ada
Organization: None

Archive-name: computer-lang/Ada/programming/part[1,2,3,4]
Comp-lang-ada-archive-name: programming/part[1,2,3,4]
Posting-Frequency: monthly
Last-modified: 25 December 1998
Last-posted: 30 September 1996

This FAQ is maintained by Magnus Kempe at the Ada Home.

Ada Programmer's
Frequently Asked Questions (FAQ)

IMPORTANT NOTE: No FAQ can substitute for real teaching and documentation. There is a list of tutorials and an annotated list of Ada books in the companion Learning Ada FAQ.

Recent changes to this FAQ are listed in the first section after the table of contents. This document is under explicit copyright.

Introduction

Ada is an advanced, modern programming language, designed and standardized to support and strongly encourage widely recognized software engineering principles: reliability, portability, modularity, reusability, programming as a human activity, efficiency, maintainability, information hiding, abstract data types, genericity, concurrent programming, object-oriented programming, etc.

All validated Ada compilers (i.e. a huge majority of the commercial Ada compilers) have passed a controlled validation process using an extensive validation suite. Ada is not a superset or extension of any other language. Ada does not allow the dangerous practices or effects of old languages, although it does provide standardized mechanisms to interface with other languages such as Fortran, Cobol, and C.

Ada is recognized as an excellent vehicle for education in programming and software engineering, including for a first programming course.

Ada is defined by an international standard (the language reference manual, or LRM), which has been revised in 1995. Ada is taught and used all around the world (not just in the USA). Ada is used in a very wide range of applications: banking, medical devices, telecommunications, air traffic control, airplanes, railroad signalling, satellites, rockets, etc.

The latest version of this FAQ is always accessible through the WWW as http://www.adahome.com/FAQ/programming.html#title

Maintenance

This FAQ is maintained on an individual volunteer basis, by Magnus Kempe (M.Kempe@ieee.org).

The coding style used in most of the example Ada code is my own, and you'll have to live with it (you may want to adopt it :-).


Opinions (if any) expressed are those of the submitters and/or maintainer.

Table of Contents:


1: Recent changes to this FAQ

What's important and missing:

(Up to Table of Contents)


2: Information about this document

This file is posted monthly to comp.lang.ada, comp.answers, and news.answers.

This document has a home on the Home of the Brave Ada Programmers (HBAP) WWW Server, in hypertext format, URL http://www.adahome.com/FAQ/programming.html#title

It is available --as posted in *.answers-- on rtfm.mit.edu, which archives all FAQ files posted to *.answers; see ftp://rtfm.mit.edu/pub/usenet-by-group/news.answers/computer-lang/Ada

The text-only version is also available in directory ftp://ftp.adahome.com/pub/FAQ

Magnus Kempe maintains this document; it's a hobby, not a job. Feedback (corrections, suggestions, ideas) about it is to be sent via e-mail to M.Kempe@ieee.org
Thanks.

In all cases, the most up-to-date version of the FAQ is the version maintained on the Ada Home (HBAP) WWW Server. Please excuse any formatting inconsistencies in the posted version of this document, as it is automatically generated from the on-line version.

(Up to Table of Contents)


3: Elementary Questions

3.1: How do I make operations directly visible without "use"ing the package?

In Ada 83, you can rename the operations in your scope.
     -- Say you have an integer type called Int in package Types
     function "<" (Left, Right : Types.Int)
       return Boolean
       renames Types."<";
     -- Make sure the profiles of the first and last "<" match!

For operators, Ada 95 introduces the "use type" clause:

     use type Types.Int; -- makes operators directly visible

(Up to Table of Contents)

3.2: How do I assign to an array of length 1?

Because of ambiguity of parentheses, named notation must be used for one-element aggregates (or, under a different angle: a positional aggregate must have more than one component).

See [RM95 4.3.3(7)] as well as the syntax rule of positional_array_aggregate in [RM95 4.3.3]; historians see [RM83 4.3(4)].

     declare
       Array_of_One : array (1..1) of Float;
     begin 
       -- Array_of_One := (10.0);   -- Won't work, parsed as an expression
                                    -- within parentheses

       Array_of_One := (1 => 10.0); -- No ambiguity here
     end;

You can't write a one-element positional aggregate in Ada. Nor a zero-element aggregate. The reason for this restriction is that it would be difficult for compilers to determine whether:

     ( exp )
is a parenthesized expression of some type, or an aggregate of an array type. If Ada had used some other notation for aggregates (say, "[...]"), then this problem would not exist.

Apparently the original requirements for Ada forbade using certain ASCII characters, like '[' and ']', because those characters were not available on all hardware. Also, certain characters are used for different purposes and glyphs in countries that need additional letters not present in ASCII.

(Up to Table of Contents)

3.3: How do I create a C-style nul-terminated string?

In a declaration block, append an ASCII.NUL to create a constant Ada string.
     declare
       Str_Nul : constant String := Str & ASCII.NUL;
     begin
       Call_Requiring_C_String (Str_Nul (Str_Nul'First)'Address);
     end;

-- or --

     function Nul_Terminate (Str : String)
       return String is
       Str_Nul : constant String := Str & ASCII.NUL;
     begin
       return Str_Nul;
     end Nul_Terminate;

(Up to Table of Contents)

3.4: How can I create an array of strings of various length?

In Ada 83, you have to use string access types and "new" to get "ragged" arrays:
     type String_Access is
       access String;
   
     Strings : constant array (Positive range 1..3) of String_Access
             := ( 1 => new String'("One"),
                  2 => new String'("Two"),
                  3 => new String'("Three")
                );

In Ada 95, the process is simplified by using aliased constants:

     type String_Access is
       access constant String;
  
     One : aliased constant String := "One";
     Two : aliased constant String := "Two";
     Three : aliased constant String := "Three";
   
     Strings : constant array (Positive range <>) of String_Access
             := ( 1 => One'Access,
                  2 => Two'Access,
                  3 => Three'Access
                );

(Up to Table of Contents)

3.5: I know an exception is raised, but my program quits with no warning. Why?

On some Ada compilers, you have to manually "with" Text_IO before exception information is displayed to the terminal (remember that Ada was designed to support embedded systems, which do not always have a CRT).

On other Ada compilers, you must set an environment variable flag in order to cause the exception information trace to be displayed.

(Up to Table of Contents)

3.6: I have only one task in my program, but it doesn't seem to run. Why?

In Ada, the main procedure is automatically designated as a task. This task may be running forever, thus starving your other task(s), because round-robin scheduling (time-slicing) is not required (pre-emptive scheduling applies to tasks with different levels of priority).

If the task in question is getting starved, it's a programmer problem, not an Ada problem. The programmer has to use an Ada compiler that supports pragma Time_Slice, or do the scheduling himself (by changing the implementation of his Ada program to ensure that no task starves another).

One solution is to explicitly put the main task to sleep within a loop construct in order to avoid starvation of the other task(s), as in:

     procedure Main is
       task Test;
       task body Test is
       begin
         loop
           delay 1.0;
           Text_IO.Put_Line ("Test");
         end loop;
       end Test;
     begin
       loop
         delay 20.0;
         Text_IO.Put_Line ("Sleeping then writing");
       end loop;
     end Main;

(Up to Table of Contents)

3.7: How do I increase the stack size for a task?

Define the task as a "task type" and then use a pragma representation clause.
     task type A_Task_Type;
     for A_Task_Type'STORAGE_SIZE use 10_000;
     -- 10K bytes allocated to instances of A_Task_Type
     A_Task : A_Task_Type;

(Up to Table of Contents)

3.8: What's the difference between a type conversion and a qualifier?

Use a qualifier (tick) to tell the compiler what type it can expect; this is strictly a compile-time issue: a qualifier "hints" the type, usually to remove an ambiguity. Use a conversion to tell the compiler to convert an expression from one type to another (usually within one derivation hierarchy); this operation may require a change of representation at run-time (e.g. in case of a representation clause applying exclusively to the source type).
     A : Integer := Integer'(1);  -- this is a qualifier: same as ":= 1;"
     B : Integer := Integer (1);  -- this is a conversion
(Up to Table of Contents)

3.9: How do I avoid the potential space in front of Integer'Image?

Use the function Trim from package Ada.Strings.Fixed (you can actually trim strings in many other useful ways):
     function My_Image (I : Integer)
       return String is
     begin -- My_Image
       return Ada.Strings.Fixed.Trim (Integer'Image (I), Ada.Strings.Left);
     end My_Image;
  
     ... My_Image (12) = "12" ...

In Ada 83, code a function that accepts a string and strips the leading blank:

     function Strip_Leading_Blank (Str : String)
       return String is
     begin -- Strip_Leading_Blank
       if Str (Str'First) = ' ' then
         return Str (1+Str'First .. Str'Last);
       else
         return Str;
       end if;
     end Strip_Leading_Blank;
  
     ...
  
     function My_Image (I : Integer)
       return String is
     begin -- My_Image
       return Strip_Leading_Blank (Integer'Image (I));
     end My_Image;
  
     ... My_Image (12) = "12" ...

(Up to Table of Contents)

3.10: Why is an exception raised when giving a default discriminant?

Let's assume you would like to model varying-length strings:
     type V_String (Size : Natural := 0) is
       record
         S : String (1 .. Size);
       end record;

(from Robert Dewar)

When you give a default discriminant, then one method (I actually think it is the preferred method) of implementation is to allocate the maximum possible length. Since your discriminant is of type Natural, this clearly won't work!

GNAT may compile it, but it won't run it, and indeed I consider it a GNAT bug (on the todo list) that no warning is issued at compile time for this misuse.

Some compilers, notably Alsys and RR, have at least partially "solved" this problem by introducing hidden pointers, but this to me is an undesirable implementation choice.

First, it means there is hidden heap activity, which seems undesirable. In a language where pointers are explicit, it is generally a good idea if allocation is also explicit, and certainly for real-time work, hidden anything is worrisome.

Second, it is not easy to do uniformly. Alsys ends up introducing arbitrary restrictions on the composition of such types (try making an array of them), and RR introduces non-contiguous representations, which are legal but troublesome.

To "solve" the problem yourself, just declare a reasonable maximum length, and use a subtype representing this length as the subtype of the discriminant:

     Max_Length : constant := 200;

     subtype Index is
       Natural range 0 .. Max_Length;

     type V_String (Size : Index := 0) is
       record
         S : String (1 .. Size);
       end record;

(Up to Table of Contents)

3.11: When I want an Integer type, what's wrong with just using the predefined type Integer or Long_Integer? Why would I ever want to declare new Integer types?

If you declare 2 distinct integer types, for example,
     type Data_Index        is range 1..100;
     type Time_Series_Index is range 0..2**15-1;

then objects of type Data_Index can't be assigned (directly) to variables of type Time_Series_Index, and vice-versa. Likewise, variables of these 2 types can't be mixed in arithmetical expressions (without explicit type conversions). This may seem like a source of endless irritation, but on the contrary, good progammers use it to improve the clarity of their code, to make it more robust, and more portable. The first 2 examples discuss this. The third example discusses the declaration of machine-portable 32-bit integers. Declaring objects of type Integer can be highly non-portable, and of course type Long_Integer may not exist on some compilers.

Example 1.

Suppose you declare arrays using the above indices:
     type Time_Series is array (Time_Series_Index) of Float;
     type Y_Axis_Data is array (Data_Index)        of Float;
     
     Measurement : Time_Series;

Now if you mistakenly try to iterate over one array with the index of the other, the compiler can catch the error at compile time:

  
     for I in Data_Index loop
        Sum := Sum + Measurement(I);  -- compilation error
     end loop;

Example 2.

This is lifted from Tucker Taft's brief introduction to Ada 95 in the contributed papers section of the Ada World Wide Web homepage. Here Tucker uses the Ada 95 unsigned integers, called modular types, in the implementation of a protected type, which defines a disk control unit. Modular types are integer types with "and", "or" and "xor" defined, so systems programmers are likely to use them as bit masks. Just as the array indices of the 2 arrays defined above are never meant to be mixed, the modular integer types used to implement the disk control unit are never meant to be mixed. To make sure the compiler enforces this, they are declared as distinct types:
     type Flags   is mod 2**4;  -- a 4-bit flags field
     type Control is mod 2**4;  -- A 4-bit control field

     Status_Mask  : constant Flags := 2#1001#;   -- Set first and last bits.
     Status_Ready : constant Flags := 2#1000#;   -- Status = Ready
    
     Start_Xfr    : constant Control := 2#0001#; -- Initiate xfr command

Now if someone attempts to apply a Flag variable where a Control variable should be used (or vice-versa) the compiler will catch the error. This is especially important when the code is maintained by programmers who did not write it.

Remarks on Examples 1 and 2.

1. Notice that in both examples the programmer was able to state his intentions rather forcefully in the code - intentions that otherwise might have been expressed much less forcefully in comment statements. Because of Ada's strong typing model, the compiler was able to catch errors at compile-time when the programmer's intentions were violated.

2. Notice also that the Integer declarations in the 2 examples are machine portable, unlike Integer and Long_Integer. A compiler will typically map these integer types onto the most efficient base type that is available on the target machine.

Example 3.

Although the examples given above are good ones, it is not necessarily a common practice to define a large number of distinct integer types. In many cases it is appropriate to use (say) a 32-bit integer (or a small number of such types) and declare appropriate subtypes of it (them). To declare a portable 32-bit integer (or more accurately, the most efficient integer that is at least 32-bits):
   type Int_tmp is range -2**31+1 .. 2**31-1;
   type Integer_32 is range Int_tmp'Base'First..Int_tmp'Base'Last;

A compiler may reject this declaration if no suitable base type is available, but this is rare. What happens is this: in order to implement Int_tmp, the compiler chooses as the base type of Int_tmp an integer type that is available on the target machine. This base type is usually the most efficient integer that accomodates the range of Int_tmp, which in turn is usually the machine's 32-bit integer. (It might even be a 64-bit integer on some machines, in which case Integer_32'Size = 64, and Integer_32'Last = 2**63-1. Maybe we should not call it Integer_32!)

(Up to Table of Contents)

3.12: Since I can always declare my own portable integer types, why would I ever want to use the predefined type Integer?

The language itself provides some guidance here. The predefined type Integer is used by Ada in the implementation of a number of convenient services. The following examples describe some of these services. Notice that in most of the following examples, it is unlikely that it will ever matter whether or not the predefined type Integer is 16-bits, 32-bits, 48-bits, or 64-bits.

a) The exponentiation of X (written X**N) is defined by the language for any floating point or integer X, provided N is of type Integer. (N should be non-negative for integer X though.)

b) Ada's predefined String type (really just a packed unconstrained array of characters) uses an index of subtype Positive (i.e. type Integer).

c) The array index in the following "short-hand" array declaration is implicitly defined to be type Integer:

     A : array(10..40) of Float;

d) The loop parameter I in the following for loop is implicitly declared type Integer:

     for I in 10..40 loop
      ...
     end loop;

This application of type Integer is the one most likely to get you into portability trouble. If you write: "for I in 1..2**17 loop", then you get a constraint error on compilers that make Integer 16-bits, because 2**17 is out of range of any Ada 16-bit integer.

(Up to Table of Contents)

3.13: I am learning Ada. Can I experiment with a game program?

Of course.

The Public Ada library (FTP wuarchive.wustl.edu) has a portable Ada Tetris program in the languages/ada/misc/games directory. It uses tasking, keyboard input, and ANSI screen graphics. Have fun!

There is also "program Small", a tiny text adventure program, that you can expand; it is documented at URL http://www.adahome.com/Tutorials/Lovelace/small.htm

(Up to Table of Contents)

3.14: How can I do a non-blocking, keystroke-at-a-time read from the terminal?

Use the procedure Text_IO.Get_Immediate [RM95 A.10.7(11)].

If you don't have an Ada 95 compiler but have a POSIX binding, there is a package using POSIX services that provides non-blocking, keystroke-at-a-time access to the terminal. It is available by FTP in file ftp://ftp.adahome.com/pub/FAQ/inkey.ada

(Up to Table of Contents)


4: Advantages of Ada

4.1: Why use Ada?

Think of it like this: We're the kid on the street corner, licking that tasty ice cream cone on a hot summer day; an impish grin decorates our face as we consume our cool confection. Meanwhile, other kids gather round, noticing our pleasure. It matters not a whit that they've just had a drink, or had their fill with supper -- they now want ice cream. We offer no lecture on how good the ice cream is, we simply demonstrate that we are happy, and let their memories carry them to the nearest ice cream truck.

(Sorry, I got a little carried away --DW).

(Up to Table of Contents)

4.2: Ada seems large and complex, why is it this way?

(Robert Dewar, lead designer of the GNU Ada compiler, responds):

During the Ada 95 development process we have often had fierce arguments over the need to simplify proposals, and I pointed out some time ago that the idea of simplicity is heavily overloaded:

None of these goals are quite the same, and often they severely conflict.

If you listen to programming language design types, especially from universities, they often have very little experience in programming, and especially little experience in writing large delivered, maintained software. That doesn't mean they know nothing about programming languages, but it does tend to mean that their view of complexity is skewed, and in particularly concentrates on the simplicity of the language itself, rather than on the simplicity of resulting programs.

A lot of the creative tension in the 9X design process arose from this same fundamental dichotomy. The design team tended to have a high tolerance for language complexity (partly because they were very good at understanding language details), but had a lot of experience in actual large scale programming, and so their idea of simplicity was biased heavily to simplifying Ada programs. The opposite voice, worried about the simplicity of the language itself, represented by a section of the DR's and ISO group (who, being a larger more diverse group tended to reflect a wider view), considered that the design team had gone too far in this direction. If you want to get a feel for the transitions, look at the early versions of the 9X ILS, particularly version 1.0.

In retrospect, I think we came up with what is at least very close the optimal balance. Tuck can speak for himself here more clearly than I can speak for him, but I would guess that he and the other members of the team recognize that you have to be able to sell the resulting design as an acceptably simple whole, and thus must step back from the most extensive proposals, while on the other hand, the more conservative KISS sentiments were convinced to accept more features than they originally felt comfortable with because of convincing programming examples and discussions of resulting programming complexity. The third wing of opinion ("I don't care what you think, but if we can't implement it, then it's not much use!") was also effectively fed in from the user-implementor teams.

Is the result too complex? Time will tell, but I think the balance is a successful blend.

(Up to Table of Contents)

4.3: Is there a contest with fame and money for good Ada programmers?

UNFORTUNATELY THE CONTEST DESCRIBED BELOW WAS CANCELLED. MAYBE A SIMILAR CONTEST WILL BE CREATED IN THE FUTURE.

Yes, they should enter the Ada Lovelace Programming Contest sponsored by the Ada Resource Association (ARA).

The Ada contest seeks to recognize the most readable, original, reusable, and clear working Ada programs. Like the Ada programming language, the contest is named in honor of the first programmer in history, Lady Ada Lovelace.

Every three months, the ARA will pay US$ 750 to the best Ada code segment submitted. Submissions must be received by the 15th (midnight) of the "contest month" and the award will be announced at the end of the second month. A submission is made by emailing the source code to

        ara-contest@ocsystems.com
The first contests closed in December 1995 and March 1996.

The rules and guidelines of the contest are available from the Ada Contest WWW Home at http://www.adahome.com/Contest/announce.html

This contest is open to all. Sharpen your designs, code, comments, and demos; show the world how good (and unobfuscated :-) your Ada code is, and win the prize!

(Up to Table of Contents)

4.4: Is Ada cost-effective?

Yes, it is.

A very important paper has been available and neglected for too long now; read "Comparing Development Costs of C and Ada", written by Stephen F. Zeigler, Ph.D., of Rational Software Corporation, available in HTML at http://www.rational.com/sitewide/support/whitepapers/dynamic.jtmpl?doc_key=337

If you know someone (programmer or manager) who doesn't understand that Ada is much more efficient, maintainable, and cost-effective than C and its scions, give them this paper to read. Maybe they'll understand the bottom line: Ada is an asset, it is profitable; and by comparison C is a liability--that's a fact.

(Up to Table of Contents)


5: Object-Oriented Programming with Ada

5.1: Why does Ada have "tagged types" instead of classes?

(Tucker Taft responds):

Someone recently asked me to explain the difference between the meaning of the term "class" in C++ and its meaning in Ada 95. Here is a synopsis of the answer:

In C++, the term "class" refers to three different, but related things:

In Ada 95, the term "class" refers only to the third of the above definitions. Ada 95 (and Ada 83) has three different terms for the concepts corresponding to the above three things:

Some OOP languages take an intermediary position. In CLOS, a "class" is not an encapsulating construct (CLOS has "packages"). However, a "class" is both a type and a set of types, depending on context. (Methods "float" freely.)

The distinction Ada 95 makes between types and classes (= set of types) carries over into the semantic model, and allows some interesting capabilities not present in C++. In particular, in Ada 95 one can declare a "class-wide" object initialized by copy from a "class-wide" formal parameter, with the new object carrying over the underlying type of the actual parameter. For example:

     procedure Print_In_Bold (X : T'Class) is
       -- Copy X, make it bold face, and then print it.
       Copy_Of_X : T'Class := X;
     begin
        Make_Bold (Copy_Of_X);
        Print (Copy_Of_X);
     end P;

In C++, when you declare an object, you must specify the "exact" class of the object -- it cannot be determined by the underlying class of the initializing value. Implementing the above procedure in a general way in C++ would be slightly more tedious.

Similarly, in Ada 95 one can define an access type that designates only one specific type, or alternatively, one can define one that can designate objects of any type in a class (a "class-wide" access type). For example:

     type Fancy_Window_Ptr is access Fancy_Window;  
       -- Only points at Fancy Windows -- no derivatives allowed
     type Any_Window_Ptr is access Window'Class;
       -- Points at Windows, and any derivatives thereof.

In C++, all pointers/references are "class-wide" in this sense; you can't restrict them to point at only one "specific" type.

In other words, C++ makes the distinction between "specific" and "class-wide" based on pointer/reference versus object/value, whereas in Ada 95, this distinction is explicit, and corresponds to the distinction between "type" (one specific type) and "class" (set of types).

The Ada 95 approach we believe (hope ;-) gives somewhat better control over static versus dynamic binding, and is less error prone since it is type-based, rather than being based on reference vs. value.

In any case, in Ada 95, C++, and CLOS it makes sense to talk about "class libraries," since a given library will generally consist of a set of interrelated types. In Ada 95 and CLOS, one could alternatively talk about a set of "reusable packages" and mean essentially the same thing.

(Up to Table of Contents)

5.2: Variant records seem like a dead feature now. When should I use them instead of tagged types?

This is an instance of a much more general question: "When should I use what kind of type?" The simple answer is: "When it makes sense to do so." The real key to chosing a type in Ada is to look at the application, and pick the type that most closely models the problem.

For instance, if you are modelling data transmission where the message packets may contain variable forms of data, a variant record --not a hierarchy of tagged types-- is an appropriate model, since there may be no relationship between the data items other than their being transmitted over one channel. If you choose to model the base type of the messages with a tagged type, that may present more problems than it solves when communicating across distinct architectures.

[More to be said about variant programming vs. incremental programming.]

(Up to Table of Contents)

5.3: What is meant by "interface inheritance" and how does Ada support it?

This answer intentionally left blank.

(Up to Table of Contents)

5.4: How do you do multiple inheritance in Ada 95?

There is a lengthy paper in file http://sw-eng.falls-church.va.us/AdaIC/docs/flyers/text/multin9x.txt

That document describes several mechanisms for achieving MI in Ada. It is not unusual, however, to find complaints about the syntax and the perceived burden it places on the developer. This is what Tucker Taft had to say when responging to such a criticism on comp.lang.ada:

Coming up with a syntax for multiple inheritance was not the challenge. The challenge was coming up with a set of straightforward yet flexible rules for resolving the well known problems associated with multiple inheritance, namely:

For answers, you can look at the various languages that define a built-in approach to multiple inheritance. Unfortunately, you will generally get a different answer for each language -- hardly a situation that suggests we will be able to craft an international consensus. Eiffel uses renaming and other techniques, which seem quite flexible, but at least in some examples, can be quite confusing (where you override "B" to change what "A" does in some distant ancestor). C++ has both non-virtual and virtual base clases, with a number of rules associated with each, and various limitations relating to downcasting and virtual base classes. CLOS uses simple name matching to control "slot" merging. Some languages require that all but one of the parent types be abstract, data-less types, so only interfaces are being inherited; however if the interfaces happen to collide, you still can end up with undesirable and potentially unresolvable collisions (where you really want different code for same-named interfaces inherited from different ancestors).

One argument is that collisions are rare to begin with, so it doesn't make much different how they are resolved. That is probably true, but the argument doesn't work too well during an open language design process -- people get upset at the most unbelievably trivial and rarely used features if not "correctly" designed (speaking from experience here ;-).

Furthermore, given that many of the predominant uses of MI (separation of interface inheritance from implementation inheritance, gaining convenient access to another class's features, has-a relationships being coded using MI for convenience, etc.) are already handled very well in Ada 95, it is hard to justify getting into the MI language design fray at all. The basic inheritance model in Ada 95 is simple and elegant. Why clutter it up with a lot of relatively ad-hoc rules to handle one particular approach to MI? For the rare cases where MI is really critical, the last thing the programmer wants in the language is the "wrong" MI approach built in.

So the basic answer is that at this point in the evolution of OO language design, it seemed wiser to provide MI building blocks, rather than to foist the wrong approach on the programmer, and be regretting it and working around it for years to come.

Perhaps [Douglas Arndt] said it best...

Final note: inheritance is overrated, especially MI. ...

If the only or primary type composition mechanism in the language is based on inheritance, then by all means, load it up. But Ada 95 provides several efficient and flexible type composition mechanisms, and there is no need to overburden inheritance with unnecessary and complicated baggage.

(Up to Table of Contents)

5.5: Why are Controlled types so, well, strange?

(Tucker Taft responds):

We considered many approaches to user-defined finalization and user-defined assignment. Ada presents challenges that make it harder to define assignment than in other languages, because assignment is used implicitly in several operations (by-copy parameter passing, function return, aggregates, object initialization, initialized allocators, etc.), and because Ada has types whose set of components can be changed as a result of an assignment.

For example:

     type T (D : Boolean := False) is record
       case D is
         when False => null;
         when True => H : In_Hands;
       end case;
     end record;

     X,Z : T;
     Y : T := (True, H => ...);

     ...

     X := Y;   -- "X.H" component coming into existence
     Y := Z;   -- "Y.H" component going out of existence

With a type like the one above, there are components that can come and go as a result of assignment. The most obvious definition of assignment would be:

     procedure ":=" (Left : in out In_Hands; Right : in In_Hands);

Unfortunately, this wouldn't work for the "H" component, because there is no preexisting "In_Hands" component to be assigned into in the first case, and in the second case, there is no "In_Hands" component to assign "from."

Therefore, we decided to decompose the operation of assignment into separable pieces: finalization of the left hand side; simple copying of the data from the right hand side to the left hand side; and then adjustment of the new left hand side. Other decompositions are probably possible, but they generally suffer from not being easily composable, or not handling situations like the variant record above.

Imagine a function named ":=" that returns a copy of its in parameter. To do anything interesting it will have to copy the in parameter into a local variable, and then "fiddle" with that local variable (essentially what "Adjust" does), and then return that local variable (which will make yet another copy). The returned result will have to be put back into the desired place (which might make yet another copy). For a large object, this might involve several extra copies.

By having the user write just that part of the operation that "fiddles" with the result after making a copy, we allow the implementation to eliminate redundant copying. Furthermore, some user-defined representations might be position dependent. That is, the final "fiddling" has to take place on the object in its final location. For example, one might want the object to point to itself. If the implementation copies an object after the user code has adjusted it, such self-references will no longer point to the right place.

So, as usual, once one gets into working out the details and all the interactions, the "obvious" proposal (such as a procedure ":=") no longer looks like the best answer, and the best answer one can find potentially looks "clumsy" (at least before you try to work out the details of the alternatives).

(Up to Table of Contents)

5.6: What do "covariance" and "contravariance" mean, and does Ada support either or both?

(From Robert Martin) [This is C++ stuff, it should be completely re-written for Ada. --MK]

 R> covariance:  "changes with"
 R> contravariance: "changes against"

 R> class A
 R> {
 R>    public:
 R>      A* f(A*);   // method of class A, takes A argument and returns A
 R>      A* g(A*);   // same.
 R> };

 R> class B : public A // class B is a subclass of class A
 R> {
 R>   public:
 R>     B* f(B*);  // method of class B overrides f and is covariant.
 R>     A* g(A*);  // method of class B overrides g and is contravariant.
 R> };

 R> The function f is covariant because the type of its return value and
 R> argument changes with the class it belongs to.  The function g is
 R> contravariant because the types of its return value and arguments does not
 R> change with the class it belongs to.

Actually, I would call g() invariant. If you look in Sather, (one of the principle languages with contravariance), you will see that the method in the decendent class actually can have arguments that are superclasses of the arguments of its parent. So for example:

class A : public ROOT
{
   public:
     A* f(A*);   // method of class A, takes A argument and returns A
     A* g(A*);   // same.
};

class B : public A // class B is a subclass of class A
{
  public:
    B* f(B*);  // method of class B overrides f and is covariant.
    ROOT* g(ROOT*);  // method of class B overrides g and is contravariant.
};

To my knowledge the uses for contravariance are rare or nonexistent. (Anyone?). It just makes the rules easy for the compiler to type check. On the other hand, co-variance is extremely useful. Suppose you want to test for equality, or create a new object of the same type as the one in hand:

class A
{
   public:
      BOOLEAN equal(A*);
      A* create();
}

class B: public A
{
   public:
      BOOLEAN equal(B*);
      B* create();
}

Here covariance is exactly what you want. Eiffel gives this to you, but the cost is giving up 100% compile time type safety. This seem necessary in cases like these.

In fact, Eiffel gives you automatic ways to make a method covariant, called "anchored types". So you could declare, (in C++/eiffese):

class A
{
   public:
      BOOLEAN equal(like Current *);
      like Current * create();
}

Which says equal takes an argument the same type as the current object, and create returns an object of the same type as current. Now, there is not even any need to redeclare these in class B. Those transformations happen for free!

(Up to Table of Contents)

5.7: What is meant by upcasting/expanding and downcasting/narrowing?

(Tucker Taft replies):

Here is the symmetric case to illustrate upcasting and downcasting.

     type A is tagged ...;   -- one parent type

     type B is tagged ...;   -- another parent type

     ...

     type C;   -- the new type, to be a mixture of A and B

     type AC (Obj : access C'Class) is
       new A
       with ...;
       -- an extension of A to be mixed into C

     type BC (Obj : access C'Class) is
       new B
       with ...;
       -- an extension of B to be mixed into C

     type C is
       tagged limited record
         A : AC (C'Access);
         B : BC (C'Access);
         ... -- other stuff if desired
       end record;

We can now pass an object of type C to anything that takes an A or B as follows (this presumes that Foobar and QBert are primitives of A and B, respectively, so they are inherited; if not, then an explicit conversion (upcast) to A and B could be used to call the original Foobar and QBert).

     XC : C;
   ...
     Foobar (XC.A);
     QBert (XC.B);

If we want to override what Foobar does, then we override Foobar on AC. If we want to override what QBert does, then we override QBert on BC.

Note that there are no naming conflicts, since AC and BC are distinct types, so even if A and B have same-named components or operations, we can talk about them and/or override them individually using AC and BC.

Upcasting (from C to A or C to B) is trivial -- A(XC.A) upcasts to A; B(XC.B) upcasts to B.

Downcasting (narrowing) is also straightforward and safe. Presuming XA of type A'Class, and XB of type B'Class:

     AC(XA).Obj.all downcasts to C'Class (and verifies XA in AC'Class)
     BC(XB).Obj.all downcasts to C'Class (and verifies XB in BC'Class)

You can check before the downcast to avoid a Constraint_Error:

     if XA not in AC'Class then -- appropriate complaint

     if XB not in BC'Class then -- ditto

The approach is slightly simpler (though less symmetric) if we choose to make A the "primary" parent and B a "secondary" parent:

     type A is ...
     type B is ...

     type C;

     type BC (Obj : access C'Class) is
       new B
       with ...

     type C is
       new A
       with record
         B : BC (C'Access);
         ... -- other stuff if desired
       end record;

Now C is a "normal" extension of A, and upcasting from C to A and (checked) downcasting from C'Class to A (or A'Class) is done with simple type conversions. The relationship between C and B is as above in the symmetric approach.

Not surprisingly, using building blocks is more work than using a "builtin" approach for simple cases that happen to match the builtin approach, but having building blocks does ultimately mean more flexibility for the programmer -- there are many other structures that are possible in addition to the two illustrated above, using the access discriminant building block.

For example, for mixins, each mixin "flavor" would have an access discriminant already:

     type Window is ...  -- The basic "vanilla" window

     -- Various mixins
     type Win_Mixin_1 (W : access Window'Class) is ...

     type Win_Mixin_2 (W : access Window'Class) is ...

     type Win_Mixin_3 (W : access Window'Class) is ...

Given the above vanilla window, plus any number of window mixins, one can construct a desired window by including as many mixins as wanted:

     type My_Window is
       new Window
       with record
         M1 : Win_Mixin_1 (My_Window'access);
         M3 : Win_Mixin_3 (My_Window'access);
         M11 : Win_Mixin_1(My_Window'access);
         ... -- plus additional stuff, as desired.
       end record;

As illustrated above, you can incorporate the same "mixin" multiple times, with no naming conflicts. Every mixin can get access to the enclosing object. Operations of individual mixins can be overridden by creating an extension of the mixin first, overriding the operation in that, and then incorporating that tweaked mixin into the ultimate window.

I hope the above helps better illustrate the use and flexibility of the Ada 95 type composition building blocks.

(Up to Table of Contents)

5.8: How does Ada do "narrowing"?

Dave Griffith said
. . . Nonetheless, [the Ada 95 designers] chose a structure-based subtyping, with all of the problems that that is known to cause. As the problems of structure based subtyping usually manifest only in large projects maintained by large groups, this is _precisely_ the subtype paradigm that Ada 95 should have avoided. Ada 95's model is, as Tucker Taft pointed out, quite easy to use for simple OO programming. There is, however, no good reason to _do_ simple OO programming. OO programmings gains click in somewhere around 10,000 LOC, with greatest gains at over 100,000. At these sizes, "just declare it tagged" will result in unmaintainable messes. OO programming in the large rapidly gets difficult with structure based subtyping. Allowing by-value semantics for objects compounds these problems. All of this is known. All of this was, seemingly, ignored by Ada 95.

(Tucker Taft answers)

As explained in a previous note, Ada 95 supports the ability to hide the implementation heritage of a type, and only expose the desired interface heritage. So we are not stuck with strictly "structure-based subtyping." Secondly, by-reference semantics have many "well known" problems as well, and the designers of Modula-3 chose to, seemingly, ignore those ;-) ;-). Of course, in reality, neither set of language designers ignored either of these issues. Language design involves tradeoffs. You can complain we made the wrong tradeoff, but to continue to harp on the claim that we "ignored" things is silly. We studied every OOP language under the sun on which we could find any written or electronic material. We chose value-based semantics for what we believe are good reasons, based on reasonable tradeoffs.

First of all, in the absence of an integrated garbage collector, by-reference semantics doesn't make much sense. Based on various tradeoffs, we decided against requiring an integrated garbage collector for Ada 95.

Secondly, many of the "known" problems with by-value semantics we avoided, by eliminating essentially all cases of "implicit truncation." One of the problems with the C++ version of "value semantics" is that on assignment and parameter passing, implicit truncation can take place mysteriously, meaning that a value that started its life representing one kind of thing gets truncated unintentionally so that it looks like a value of some ancestor type. This is largely because the name of a C++ class means differnt things depending on the context. When you declare an object, the name of the class determines the "exact class" of the object. The same thing applies to a by-value parameter. However, for references and pointers, the name of a class stands for that class and all of its derivatives. But since, in C++, a value of a subclass is always acceptable where a value of a given class is expected, you can get implicit truncation as part of assignment and by-value parameter passing. In Ada 95, we avoid the implicit truncation because we support assignment for "class-wide" types, which never implicitly truncates, and one must do an explicit conversion to do an assignment that truncates. Parameter passing never implicitly truncates, even if an implicit conversion is performed as part of calling an inherited subprogram.

(Up to Table of Contents)

5.9: What is the difference between a class-wide access type and a "general" class-wide access type?

What is exactly the difference between
 
type A is access Object'Class;
and
 
type B is access all Object'Class;
In the RM and Rationale only definitions like B are used. What's the use for A-like definitions ?

(Tucker Taft answers)

The only difference is that A is more restrictive, and so presumably might catch bugs that B would not. A is a "pool-specific" access type, and as such, you cannot convert values of other access types to it, nor can you use 'Access to create values of type A. Values of type A may only point into its "own" pool; that is only to objects created by allocators of type A. This means that unchecked-deallocation is somewhat safer when used with a pool-specific type like A.

B is a "general" access type, and you can allocate in one storage pool, and then convert the access value to type B and store it into a variable of type B. Similarly, values of type B may point at objects declared "aliased."

When using class-wide pointer types, type conversion is sometimes used for "narrowing." This would not in general be possible if you had left out the "all" in the declaration, as in the declaration of A. So, as a general rule, access-to-classwide types usually need to be general access types. However, there is no real harm in starting out with a pool-specific type, and then if you find you need to do a conversion or use 'Access, the compiler should notify you that you need to add the "all" in the declaration of the type. This way you get the added safety of using a pool-specific access type, until you decide explicitly that you need the flexibility of general access types.

In some implementations, pool-specific access types might have a shorter representation, since they only need to be able to point at objects in a single storage pool. As we move toward 64-bit address spaces, this might be a significant issue. I could imagine that pool-specific access types might remain 32-bits in some implementations, while general access types would necessarily be 64-bits.

(Up to Table of Contents)


6: Ada Numerics

6.1: Where can I find anonymous ftp sites for Ada math packages? In particular where are the random number generators?

ftp.rational.com
Freeware version of the ISO math packages on Rational's FTP server. It's a binding over the C Math library, in public/apex/freeware/math_lib.tar.Z

archimedes.nosc.mil
Stuff of high quality in pub/ada The random number generator and random deviates are recommended. These are mirrored at the next site, wuarchive.

wuarchive.wustl.edu
Site of PAL, the Public Ada Library: math routines scattered about in the directories under languages/ada in particular, in subdirectory swcomps

source.asset.com
This is not an anonymous ftp site for math software. What you should do is log on anonymously under ftp, and download the file asset.faq from the directory pub. This will tell you how to get an account.

ftp.cs.kuleuven.ac.be
Go to directory pub/Ada-Belgium/cdrom. There's a collection of math intensive software in directory swcomps. Mirrors some of PAL at wuarchive.wustl.edu.

sw-eng.falls-church.va.us
Go to directory public/AdaIC/source-code/bindings/ADAR-bindings to find extended-precision decimal arithmetic (up to 18 digits). Includes facilities for COBOL-like formatted output.

(Up to Table of Contents)

6.2: How can I write portable code in Ada 83 using predefined types like Float and Long_Float? Likewise, how can I write portable code that uses Math functions like Sin and Log that are defined for Float and Long_Float?

(from Jonathan Parker)

Ada 83 was slow to arrive at a standard naming convention for elementary math functions and complex numbers. Furthermore, you'll find that some compilers call the 64-bit floating point type Long_Float; other compilers call it Float. Fortunately, it is easy to write programs in Ada that are independent of the naming conventions for floating point types and independent of the naming conventions of math functions defined on those types.

One of the cleanest ways is to make the program generic:

     generic
       type Real is digits <>;
       with function Arcsin (X : Real) return Real is <>;
       with function    Log (X : Real) return Real is <>;
       --  This is the natural log, inverse of Exp(X), sometimes written Ln(X).
     package Example_1 is
       ...
     end Example_1;

So the above package doesn't care what the name of the floating point type is, or what package the Math functions are defined in, just as long as the floating point type has the right attributes (precision and range) for the algorithm, and likewise the functions. Everything in the body of Example_1 is written in terms of the abstract names, Real, Arcsin, and Log, even though you instantiate it with compiler specific names that can look very different:

      package Special_Case is new Example_1 (Long_Float, Asin, Ln);

The numerical algorithms implemented by generics like Example_1 can usually be made to work for a range of floating point precisions. A well written program will perform tests on Real to reject instantiations of Example_1 if the floating points type is judged inadequate. The tests may check the number of digits of precision in Real (Real'Digits) or the range of Real (Real'First, Real'Last) or the largest exponent of the set of safe numbers (Real'Safe_Emax), etc. These tests are often placed after the begin statement of package body, as in:

     package body Example_1 is
       ...
     begin
       if (Real'Machine_Mantissa > 60) or (Real'Machine_Emax < 256) then
         raise Program_Error;
       end if;
     end Example_1;

Making an algorithm as abstract as possible, (independent of data types as much as possible) can do a lot to improve the quality of the code. Support for abstraction is one of the many things Ada-philes find so attractive about the language. The designers of Ada 95 recognized the value of abstraction in the design of numeric algorithms and have generalized many of the features of the '83 model. For example, no matter what floating point type you instantiate Example_1 with, Ada 95 provides you with functions for examining the exponent and the mantissas of the numbers, for truncating, determining exact remainders, scaling exponents, and so on. (In the body of Example_1, and in its spec also of course, these functions are written, respectively: Real'Exponent(X), Real'Fraction(X), Real'Truncation(X), Real'Remainder(X,Y), Real'Scaling(X, N). There are others.) Also, in package Example_1, Ada 95 lets you do the arithmetic on the base type of Real (called Real'Base) which is liable to have greater precision and range than type Real.

It is rare to see a performance loss when using generics like this. However, if there is an unacceptable performance hit, or if generics cannot be used for some other reason, then subtyping and renaming will do the job. Here is an example of renaming:

     with Someones_Math_Lib;
     procedure Example_2 is

       subtype Real is Long_Float;

       package  Math renames Someones_Math_Lib;
       function Arcsin(X : Real) return Real renames Math.Asin
       function   Log (X : Real) return Real renames Math.  Ln;

       --  Everything beyond this point is abstract with respect to
       --  the names of the floating point (Real), the functions (Arcsin
       --  and Log), and the package that exported them (Math).
       ...
     end Example_2;

I prefer to make every package and subprogram (even test procedures) as compiler independent and machine portable as possible. To do this you move all of the renaming of compiler dependent functions and all of the "withing" of compiler dependent packages to a single package. In the example that follows, its called Math_Lib_8. Math_Lib_8 renames the 8-byte floating point type to Real_8, and makes sure the math functions follow the Ada 95 standard, at least in name. In this approach Math_Lib_8 is the only compiler dependent component.

There are other, perhaps better, ways also. See for example, "Ada In Action", by Do-While Jones for a generic solution.

Here's the spec of Math_Lib_8, which is a perfect subset of package Math_Env_8, available by FTP in file ftp://ftp.adahome.com/pub/FAQ/math_env_8.ada

--***************************************************************
-- Package Math_Lib_8
--
-- A minimal math package for Ada 83: creates a standard interface to vendor
-- specific double-precision (8-byte) math libraries.  It renames the 8 byte
-- Floating point type to Real_8, and uses renaming to create
-- (Ada 95) standard names for Sin, Cos, Log, Sqrt, Arcsin, Exp,
-- and Real_8_Floor, all defined for Real_8.
--
-- A more ambitious but perhaps less efficient
-- package would wrap the compiler specific functions in function calls, and
-- do error handling on the arguments to Ada 95 standards.
--
-- The package assumes that Real_8'Digits > 13, and that
-- Real_8'Machine_Mantissa < 61.  These are asserted after the
-- begin statement in the body.
--
-- Some Ada 83 compilers don't provide Arcsin, so a rational-polynomial+
-- Newton-Raphson method Arcsin and Arccos pair are provided in the body.
--
-- Some Ada 83 compilers don't provide for truncation of 8 byte floats.
-- Truncation is provided here in software for Compilers that don't have it.
-- The Ada 95 function for truncating (toward neg infinity) is called 'Floor.
--
-- The names of the functions exported below agree with the Ada 95 standard,
-- but not, in all likelihood the semantics.   It is up to the user to
-- be careful...to do his own error handling on the arguments, etc.
-- The performance of these function can be non-portable,
-- but in practice they have their usual meanings unless you choose
-- weird arguments.  The issues are the same with most math libraries.
--***************************************************************

--with Math_Lib;                                  -- Meridian DOS Ada.
  with Long_Float_Math_Lib;                       -- Dec VMS
--with Ada.Numerics.Generic_Elementary_Functions; -- Ada95
package Math_Lib_8 is

--subtype Real_8 is Float;                        -- Meridian 8-byte Real
  subtype Real_8 is Long_Float;                   -- Dec VMS  8-byte Real

 --package Math renames Math_Lib;                 -- Meridian DOS Ada
   package Math renames Long_Float_Math_Lib;      -- Dec VMS
 --package Math is new Ada.Numerics.Generic_Elementary_Functions(Real_8);
 
   --  The above instantiation of the Ada.Numerics child package works on
   --  GNAT, or any other Ada 95 compiler.  Its here if you want to use
   --  an Ada 95 compiler to compile Ada 83 programs based on this package.

   function Cos (X : Real_8) return Real_8 renames Math.Cos;
   function Sin (X : Real_8) return Real_8 renames Math.Sin;
   function Sqrt(X : Real_8) return Real_8 renames Math.Sqrt;
   function Exp (X : Real_8) return Real_8 renames Math.Exp;
   
 --function Log (X : Real_8) return Real_8 renames Math.Ln;        -- Meridian
   function Log (X : Real_8) return Real_8 renames Math.Log;       -- Dec VMS
 --function Log (X : Real_8) return Real_8 renames Math.Log;       -- Ada 95
   
 --function Arcsin (X : Real_8) return Real_8 renames Math.Asin;   -- Dec VMS
 --function Arcsin (X : Real_8) return Real_8 renames Math.Arcsin; -- Ada 95
   function Arcsin (X : Real_8) return Real_8;
   --  Implemented in the body.  Should work with any compiler.
   
 --function Arccos (X : Real_8) return Real_8 renames Math.Acos;   -- Dec VMS
 --function Arccos (X : Real_8) return Real_8 renames Math.Arccos; -- Ada 95
   function Arccos (X : Real_8) return Real_8;
   --  Implemented in the body.  Should work with any compiler.
   
 --function Real_8_Floor (X : Real_8) return Real_8 renames Real_8'Floor;-- 95
   function Real_8_Floor (X : Real_8) return Real_8;
   --  Implemented in the body.  Should work with any compiler.
   
end Math_Lib_8;

(Up to Table of Contents)

6.3: Is Ada any good at numerics, and where can I learn more about it?

First of all, a lot of people find the general Ada philosophy (modularity, strong-typing, readable syntax, rigorous definition and standardization, etc.) to be a real benefit in numerical programming, as well as in many other types of programming. But Ada --and especially Ada 95-- was also designed to meet the special requirements of number-crunching applications.

The following sketches out some of these features. Hopefully a little of the flavor of the Ada philosophy will get through, but the best thing you can do at present is to read the two standard reference documents, the Ada 95 Rationale and Reference Manual. Below the GNU Ada 95 compiler is referred to several times. This compiler can be obtained by anonymous FTP from cs.nyu.edu, and at mirror sites declared in the README file of directory pub/gnat.

1. Machine portable floating point declarations. (Ada 83 and Ada 95)
If you declare "type Real is digits 14", then type Real will guarantee you (at least) 14 digits of precision independently of machine or compiler. In this case the base type of type Real will usually be the machine's 8-byte floating point type. If an appropriate base type is unavailable (very rare), then the declaration is rejected by the compiler.

2. Extended precision for initialization of floating point. (Ada 83 and Ada 95)
Compilers are required to employ extended-precision/rational-arithmetic routines so that floating point variables and constants can be correctly initialized to their full precision.

3. Generic packages and subprograms. (Ada 83 and Ada 95)
Algorithms can be written so that they perform on abstract representations of the data structure. Support for this is provided by Ada's generic facilities (what C++ programmers would call templates).

4. User-defined operators and overloaded subprograms. (Ada 83 and Ada 95)
The programmer can define his own operators (functions like "*", "+", "abs", "xor", "or", etc.) and define any number of subprograms with the same name (provided they have different argument profiles).

5. Multitasking. (Ada 83 and Ada 95)
Ada facilities for concurrent programming (multitasking) have traditionally found application in simulations and distributed/parallel programming. Ada tasking is an especially useful ingredient in the Ada 95 distributed programming model, and the combination of the two makes it possible to design parallel applications that have a high degree of operating system independence and portability. (More on this in item 6 below.)

6. Direct support for distributed/parallel computing in the language. (Ada 95)
Ada 95 is probably the first internationally standardized language to combine in the same design complete facilities for multitasking and parallel programming. Communication between the distributed partitions is via synchronous and asynchronous remote procedure calls.

Good discussion, along with code examples, is found in the Rationale, Part III E, and in the Ada 95 Reference Manual, Annex E. See also "Ada Letters", Vol. 13, No. 2 (1993), pp. 54 and 78, and Vol. 14, No. 2 (1994), p. 80. (Full support for these features is provided by compilers that conform to the Ada 95 distributed computing Annex. This conformance is optional, but for instance GNAT, the Gnu Ada 95 compiler, will meet these requirements.)

7. Attributes of floating point types. (Ada 83 and Ada 95)
For every floating point type (including user defined types), there are built-in functions that return the essential characteristics of the type. For example, if you declare "type Real is digits 15" then you can get the max exponent of objects of type Real from Real'Machine_Emax. Similarly, the size of the Mantissa, the Radix, the largest Real, and the Rounding policy of the arithmetic are given by Real'Machine_Mantissa, Real'Machine_Radix, Real'Last, and Real'Machine_Rounds. There are many others.

(See Ada 95 Reference Manual, clause 3.5, subclause 3.5.8 and A.5.3, as well as Part III sections G.2 and G.4.1 of the Ada 95 Rationale.)

8. Attribute functions for floating point types. (Ada 95)
For every floating point type (including user defined types), there are built-in functions that operate on objects of that type. For example, if you declare "type Real is digits 15" then Real'Remainder (X, Y) returns the exact remainder of X and Y: X - n*Y where n is the integer nearest X/Y. Real'Truncation(X), Real'Max(X,Y), Real'Rounding(X) have the usual meanings. Real'Fraction(X) and Real'Exponent(X) break X into mantissa and exponent; Real'Scaling(X, N) is exact scaling: multiplies X by Radix**N, which can be done by incrementing the exponent by N, etc. (See citations in item 7.)

9. Modular arithmetic on integer types. (Ada 95)
If you declare "type My_Unsigned is mod N", for arbitrary N, then arithmetic ("*", "+", etc.) on objects of type My_Unsigned returns the results modulo N. Boolean operators "and", "or", "xor", and "not" are defined on the objects as though they were arrays of bits (and likewise return results modulo N). For N a power of 2, the semantics are similar to those of C unsigned types.

10. Generic elementary math functions for floating point types. (Ada 95)
Required of all compilers, and provided for any floating point type: Sqrt, Cos, Sin, Tan, Cot, Exp, Sinh, Cosh, Tanh, Coth, and the inverse functions of each of these, Arctan, Log, Arcsinh, etc. Also, X**Y for floating point X and Y. Compilers that conform to the Numerics Annex meet additional accuracy requirements.

(See subclause A.5.1 of the Ada 95 RM, and Part III, Section A.3 of the Ada 95 Rationale.)

11. Complex numbers. (Ada 95)
Fortran-like, but with a new type called Imaginary. Type "Imaginary" allows programmers to write expressions in such a way that they are easier to optimize, more readable and appear in code as they appear on paper. Also, the ability to declare object of pure imaginary type reduces the number of cases in which premature type conversion of real numbers to complex causes floating point exceptions to occur. (Provided by compilers that conform to the Numerics Annex. The Gnu Ada 95 compiler supports this annex, so the source code is freely available.)

12. Generic elementary math functions for complex number types. (Ada 95)
Same functions supported for real types, but with complex arguments. Standard IO is provided for floating point types and Complex types. (Only required of compilers that support the Numerics Annex, like Gnu Ada.)

13. Pseudo-random numbers for discrete and floating point types. (Ada 95)
A floating point pseudo-random number generator (PRNG) provides output in the range 0.0 .. 1.0. Discrete: A generic PRNG package is provided that can be instantiated with any discrete type: Boolean, Integer, Modular etc. The floating point PRNG package and instances of the (discrete) PRNG package are individually capable of producing independent streams of random numbers. Streams may be interrupted, stored, and resumed at later times (generally an important requirement in simulations). In Ada it is considered important that multiple tasks, engaged for example in simulations, have easy access to independent streams of pseudo random numbers. The Gnu Ada 95 compiler provides the cryptographically secure X**2 mod N generator of Blum, Blum and Shub.

(See subclause A.5.2 of the Ada 95 Reference Manual, and part III, section A.3.2 of the Ada Rationale.)

14. Well-defined interfaces to Fortran and other languages. (Ada 83 and Ada 95)
It has always been a basic requirement of the language that it provide users a way to interface Ada programs with foreign languages, operating system services, GUI's, etc. Ada can be viewed as an interfacing language: its module system is composed of package specifications and separate package bodies. The package specifications can be used as strongly-type interfaces to libraries implemented in foreign languages, as well as to package bodies written in Ada. Ada 95 extends on these facilities with package interfaces to the basic data structures of C, Fortran, and COBOL and with new pragmas. For example, "pragma Convention(Fortran, M)" tells the compiler to store the elements of matrices of type M in the Fortran column-major order. (This pragma has already been implemented in the Gnu Ada 95 compiler. Multi- lingual programming is also a basic element of the Gnu compiler project.) As a result, assembly language BLAS and other high performance linear algebra and communications libraries will be accessible to Ada programs.

(See Ada 95 Reference Manual: clause B.1 and B.5 of Annex B, and Ada 95 Rationale: Part III B.)

(Up to Table of Contents)

6.4: How do I get Real valued and Complex valued math functions in Ada 95?

(from Jonathan Parker)

Complex type and functions are provided by compilers that support the numerics Annex. The packages that use Float for the Real number and for the Complex number are:

     Ada.Numerics.Elementary_Functions;
     Ada.Numerics.Complex_Types;          
     Ada.Numerics.Complex_Elementary_Functions;

The packages that use Long_Float for the Real number and for the Complex number are:

     Ada.Numerics.Long_Elementary_Functions;
     Ada.Numerics.Long_Complex_Types;
     Ada.Numerics.Long_Complex_Elementary_Functions;

The generic versions are demonstrated in the following example. Keep in mind that the non-generic packages may have been better tuned for speed or accuracy. In practice you won't always instantiate all three packages at the same time, but here is how you do it:

     with Ada.Numerics.Generic_Complex_Types;
     with Ada.Numerics.Generic_Elementary_Functions;
     with Ada.Numerics.Generic_Complex_Elementary_Functions;

     procedure Do_Something_Numerical is

       type Real_8 is digits 15;
  
       package Real_Functions_8 is 
         new Ada.Numerics.Generic_Elementary_Functions (Real_8);

       package Complex_Nums_8 is
         new Ada.Numerics.Generic_Complex_Types (Real_8);

       package Complex_Functions_8 is
         new Ada.Numerics.Generic_Complex_Elementary_Functions
           (Complex_Nums_8);

       use Real_Functions_8, Complex_Nums_8, Complex_Functions_8;
       ...
       ... -- Do something
       ...
     end Do_Something_Numerical;

(Up to Table of Contents)

6.5: What libraries or public algorithms exist for Ada?

An Ada version of Fast Fourier Transform is available. It's in journal "Computers & Mathematics with Applications," vol. 26, no. 2, pp. 61-65, 1993, with the title:

"Analysis of an Ada Based Version of Glassman's General N Point Fast Fourier Transform"

The package is now available in the AdaNET repository, object #: 6728, in collection: Transforms. If you're not an AdaNET user, contact Peggy Lacey (lacey@rbse.mountain.net).

(Up to Table of Contents)


7: Efficiency of Ada Constructs

7.1: How much extra overhead do generics have?

If you overgeneralize the generic, there will be more work to do for the compiler. How do you know when you have overgeneralized? For instance, passing arithmetic operations as parameters is a bad sign. So are boolean or enumeration type generic formal parameters. If you never override the defaults for a parameter, you probably overengineered.

Code sharing (if implemented and requested) will cause an additional overhead on some calls, which will be partially offset by improved locality of reference. (Translation, code sharing may win most when cache misses cost most.) If a generic unit is only used once in a program, code sharing always loses.

R.R. Software chose code sharing as the implementation for generics because 2 or more instantiations of Float_Io in a macro implementation would have made a program too large to run in the amount of memory available on the PC machines that existed in 1983 (usually a 128k or 256k machine).

Generics in Ada can also result in loss of information which could have helped the optimizer. Since the compiler is not restricted by Ada staticness rules within a single module, you can often avoid penalties by declaring (or redeclaring) bounds so that they are local:

 
     package Global is
       subtype Global_Int is
         Integer range X..Y;

       ...
     end Global;


     with Global;
     package Local is
       subtype Global_Int is
         Global.Global_Int;

       package Some_Instance is
         new Foo (Global_Int);

       ...
     end Local;

Ada rules say that having the subtype redeclared locally does not affect staticness, but on a few occasions optimizers have been caught doing a much better job. Since optimizers are constantly changing, they may have been caught just at the wrong time.

(Up to Table of Contents)

7.2: How does Ada compare to other languages in efficiency of code?

Ada vs. C: An analysis at Tartan found that Ada and C had fairly similar performance, with Ada having a slight edge. See "C vs. Ada: Arguing Performance Religion" by David Syiek, ACM Ada Letters, Nov/Dec 1995 (Volume XV Number 6), pp. 67-69.

Ada vs. assembly language: There is a documented case where an Ada compiler and a novice Ada programmer did better than experienced assembly language programmers. See "Ada Whips Assembly" by Elam and Lawlis, Crosstalk, March 1992. Published by the Software Technology Support Center, Hill Air Force Base, Utah: Defense Printing Service.

(Up to Table of Contents)


8: Advanced Programming Techniques with Ada

(Up to Table of Contents)

8.1: How can I redefine the assignment operation?

The general answer is: use controlled types (RM95-7.6).

For detailed explanations, read the following papers:

8.2: Does Ada have automatic constructors and destructors?

Yes, controlled types have special, user-definable operations that control the construction and destruction of objects and values of those types (see question 8.1, above).

(Also: Tucker Taft replies)
At least in Ada 95, functions with controlling results are inherited (even if overriding is required), allowing their use with dynamic binding and class-wide types. In most other OOPs, constructors can only be called if you know at compile time the "tag" (or equivalent) of the result you want. In Ada 95, you can use the tag determined by the context to control dispatching to a function with a controlling result. For example:

     type Set is abstract tagged private;
     function  Empty return Set is abstract;
     function  Unit_Set(Element : Element_Type) return Set is abstract;
     procedure Remove(S : in out Set; Element : out Element_Type) is abstract;
     function  Union(Left, Right : Set) return Set is abstract;
  ...

     procedure Convert(Source : Set'Class; Target : out Set'Class) is
       -- class-wide "convert" routine, can convert one representation
       --   of a set into another, so long as both set types are
       --   derived from "Set," either directly or indirectly.

       -- Algorithm:  Initialize Target to the empty set, and then
       --             copy all elements from Source set to Target set.

        Copy_Of_Source : Set'Class := Source;
        Element : Element_Type;
     begin
        Target := Empty;  -- Dispatching for Empty determined by Target'Tag.

        while Copy_Of_Source /= Empty loop  
                       -- Dispatching for Empty based on Copy_Of_Source'Tag

            Remove_Element(Copy_Of_Source, Element);

            Target := Union(Target, Unit_Set(Element));
                       -- Dispatching for Unit_Set based on Target'Tag
        end loop;
     end Convert;

The functions Unit_Set and Empty are essentially "constructors" and hence must be overridden in every extension of the abstract type Set. However, these operations can still be called with a class-wide expected type, and the controlling tag for the function calls will be determined at run-time by the context, analogous to the kind of (compile-time) overload resolution that uses context to disambiguate enumeration literals and aggregates.

(Up to Table of Contents)

8.3: Should I stick to a one package, one type approach while writing Ada software?

(Robb Nebbe responds)

Offhand I can think of a couple of advantages arising from Ada's separation of the concepts of type and module.

Separation of visibility and inheritance allows a programmer to isolate a derived type from the implementation details of its parent. To put it another way information hiding becomes a design decision instead of a decision that the programming language has already made for you.

Another advantage that came "for free" is the distinction between subtyping and implementation inheritance. Since modules and types are independent concepts the interaction of the facilities for information hiding already present in Ada83 with inheritance provide an elegant solution to separating subtyping from implementation inheritance. (In my opinion more elegant than providing multiple forms of inheritance or two distinct language constructs.)

(Up to Table of Contents)

8.4: What is the "Beaujolais Effect"?

The "Beaujolais Effect" is detrimental, and language designers should try to avoid it. But what is it?

(from Tucker Taft)

The term "Beaujolais Effect" comes from a prize (a bottle of Beaujolais) offered by Jean Ichbiah during the original Ada design process to anyone who could find a situation where adding or removing a single "use" clause could change a program from one legal interpretation to a different legal interpretation. (Or equivalently, adding or removing a single declaration from a "use"d package.)

At least one bottle was awarded, and if the offer was still open, a few more might have been awarded during the Ada 9X process. However, thanks to some very nice analysis by the Ada 9X Language Precision Team (based at Odyssey Research Associates) we were able to identify the remaining cases of this effect in Ada 83, and remove them as part of the 9X process.

The existing cases in Ada 83 had to do with implicit conversion of expressions of a universal type to a non-universal type. The rules in Ada 95 are subtly different, making any case that used to result in a Beaujolais effect in Ada 83, illegal (due to ambiguity) in Ada 95.

The Beaujolais effect is considered "harmful" because it is expected that during maintenance, declarations may be added or removed from packages without being able to do an exhaustive search for all places where the package is "use"d. If there were situations in the language which resulted in Beaujolais effects, then certain kinds of changes in "use"d packages might have mysterious effects in unexpected places.

(from Jean D. Ichbiah)

It is worth pointing that many popular languages have Beaujolais effect: e.g. the Borland Pascal "uses" clause, which takes an additive, layer-after-layer, interpretation of what you see in the used packages (units) definitely exhibits a Beaujolais effect.

Last time I looked at C++, my impression was that several years of Beaujolais vintage productions would be required.

For component-based software development, such effects are undesirable since your application may stop working when you recompile it with the new -- supposedly improved -- version of a component.

(Up to Table of Contents)

8.5: What about the "Ripple Effect"?

(Tucker Taft explains)

We have eliminated all remnants of the Beaujolais Effect, but we did debate various instances of the "Ripple" effect during the language revision process (apologies to Gallo Ripple Wine enthusiasts ;-).

In brief, the (undesirable) Ripple effect was related to whether the legality of a compilation unit could be affected by adding or removing an otherwise unneeded "with" clause on some compilation unit on which the unit depended, directly or indirectly.

This issue came up at least twice. One when we were considering rules relating to use of attributes like 'Address. In Ada 83 as interpreted by the ARG, if a compilation unit contains a use of 'Address, then there must be a "with" of package System somewhere in the set of library unit specs "with"ed by the compilation unit (directly or indirectly).

In Ada 95, we have eliminated this rule, as it was for some compilers an unnecessary implementation burden, and didn't really provide any value to the user (if anything, it created some confusion). The rule now is that the use of an attibute that returns a value of some particular type makes the compilation unit semantically dependent on the library unit in which the type is declared (whether or not it is "with"ed).

The second place the Ripple effect came up was when we were trying to provide automatic direct visibility to (primitive) operators. Ultimately we ended up with an explicit "use type" clause for making operators directly visible. For a while we considered various rules that would make all primitive operators directly visible; some of the rules considered created the undesirable "Ripple" effects; others created annoying incompatibilities; all were quite tricky to implement correctly and efficiently.

(Up to Table of Contents)

8.6: How to write an Ada program to compute when one has had too much alcohol to legally drive?

Someone asked if there is an Ada archive of this sort of program. Each drink has a number of units of alcohol, max legal level, etc.

(from Bob Kitzberger :-)

Oh, this is much to vague. Don't touch that whizzy development environment until you fully analyze the problem domain (unless that whizzy development environment includes Rose, in which case, you get to avoid paper and pencil from the git-go).

Let's see, we have several classes to describe before we get to the implementation:

Person
subclass Drinker

attributes: weight, age, timeline for amount consumed

Drink
attributes: percentage of alcohol, quantity of drink

Country
attributes: legal age to drink; max legal level of alcohol in blood

Turn on the stereo, perhaps the Brandenburg Concertos. Then, flesh out the domain classes. Then, have a Belgian beer and consider what to do next. You decide on implementing these classes in a simple way, leading to your first successful prototype. Then, have another beer and decide what to do next. "Identify risk areas" you mutter to yourself, and off you go...

If the beer wasn't too strong, you'd probably realize that the only thing of any difficulty in this is the amount consumed / rate of decay. Decide on investigating this aspect further. Create implementation classes for this and include a reference from the Drinker class to this new timeline/decay Class. Have another beer. Implement your second prototype. Congratulate yourself for making progress so quickly.

Have another beer. Wander over to the stereo and change the CD to something more in the mood, maybe some Hendrix or Stevie Ray Vaughn. Back in front of the computer; pop another beer. Decide that it would be very cool if each drink was its own subclass of drink, and start cataloguing every drink out of your "Pocket Bartender's Guide". Have a slightly muddled epiphany that you really should create a class for each kind of alcohol (vodka, tequila, etc.) and the individual drink classes should each multiply inherit from all relevant Alcohol classes. Ooh, this is going to be a bit rough, so you have another beer. Draw a few of the hundreds of new class relationships needed, put that on the back burner when you think "persistence! that's what's missing!" Change the CD to Kraftwerk. Start your PPP connection, ask the people on comp.object for recommendations on a good OODBMS to use to keep track of all of those persistent objects. Make many many typos in your posting; everyone ignores it. Fall asleep on the keyboard.

(Up to Table of Contents)

8.7: Does Ada have macros?

No, neither Ada 83 nor Ada 95 do. There was a Steelman requirement that the language developed NOT have a macro capability. This was a well thought-out requirement. What you see in a piece of Ada code is what you get (within a debugger for example). This does not hold true for macro languages.

General text-substitution macros like those in the C preprocessor are thought to be too unsafe. For example, a macro can refer to a variable X and depending where the macro is expanded X may or may not be visible. Ada programs are supposed to be readable and in many cases C macros are the main culprits in producing unreadable C programs.

Compile time macro facilities tend to be dreadfully over- and misused, resulting in horrible maintenance problems. Furthermore, there is a tendency to use macros to patch up glaring omissions in the language. For example, C has no named constants, a very bad omission, but #define is used to patch over this gap.

In C, three "legitimate" uses of macros are for defining compile-time constants, types, and inline functions. Ada has all three of these facilities, without macros.

If one wants macros to handle conditional compilation, the better way to achieve the equivalent is in most instances to isolate the system dependent parts and then put them in separate units with multiple system-specific implementations.

(Up to Table of Contents)

8.8: Freezing rules give me headaches; is there a simple explanation?

You're not alone :-) and fortunately the basic explanation is simple.

Consider the following package:

     package P is
        type integer_8 is range 0..255;
        for integer_8'SIZE use 8;
        type integer_12 is range 0..4095;
        for integer_12'SIZE use 12;

        type rec1 is record
            field1 : integer_12;
            field2 : integer_12;
            field3 : integer_8;
        end record;

        for rec1 use record
            field1 at 0 range 0..11;
            field2 at 0 range 12..23;
            field3 at 0 range 24..31;
        end record;

        type array1 is array (0..9) of rec1;

        for rec1'size use 48;
        type array2 is array (0..9) of rec1;

    end P;

Note: we use Ada 95 terminology in discussing it, but the principle was the same in Ada 83 [just the terminology is different, using the idea of "forcing occurrences" instead of freezing].

Some uses of a subtype name, most notably its use in a variable declaration "freeze" the type. The idea of freezing is that this is the point at which the compiler knows enough about the type to choose a final representation and needs to do so (hence the headaches: human beings are usually not trained to think like compilers).

Not all uses of subtype names freeze; in particular, the use of rec1 as the component type for array1 and array2 does NOT freeze rec1; instead, a component type freezes (if it is not already frozen) at the point where the array type freezes. (Note: no, one cannot reverse freezing by "melting" declarations :-).

In this code, none of the types are frozen until the end statement (which freezes all types declared in a library package that are not yet frozen). Hence at the point at which array1 and array2 are frozen, the size of rec1 is set, and the two arrays would have the same representation.

If we change the source a bit:

       type array1 is array (0..9) of rec1;

       A1 : array1; -- ADD a variable declaration, freezing!

       for rec1'size use 48;
       type array2 is array (0..9) of rec1;
then the program is illegal, e.g. GNAT says
    20.         type array1 is array (0..9) of rec1;
    21.
    22.         A1 : array1;
                |
        >>> warning: no more rep clauses for type "rec1" declared at line 8

    23.
    24.         for rec1'size use 48;
                    |
        >>> rep clause appears too late

    25.         type array2 is array (0..9) of rec1;
    26.
    27.     end P;
The declaration at line 22 freezes array1, and also freezes its component type rec1, so it is then too late to set the size of rec1.

(Up to Table of Contents)


9: Ada and Other Programming Languages

9.1: Where can I find programs that will translate from [some language] to Ada?

It is generally advisable to simply interface from Ada to the existing code segments that (should) already work. Note that Ada (95) now has an annex devoted to specifying how to interface with code written in other programming languages (C, Fotran, and Cobol), and there are already interfaces to C++ too.

Another option is to redesign the code, taking of course advantage of one's knowledge of the current system. For instance, Job Honig reported that he did this twice, once for Coco, a parser generator for LALR left attributed grammars, and once for Flex, the well known scanner generator. Both attempts revealed errors in the original software; they were uncovered by designing the new system using the higher abstraction level allowed by Ada...

So there is support for the requirements analysis (transition to Ada), but it is not obvious that the proposed implementation (using a source code translator) is a good solution.

Still, you may have compelling reasons to translate your existing source to Ada. In that case, here is a list of available translators:

(Up to Table of Contents)

9.2: How can I convert Ada 83 sources to Ada 95?

First you should read the following document, which will provide you with much useful information: "Changes to Ada -- 1987 to 1995", file ch83.{ps,doc}, in directory ftp://sw-eng.falls-church.va.us/public/AdaIC/standards/95lrm_rat/v6.0

If you're using GNAT, the tool you are probably looking for is "gnatchop". In csh you could use something like this to quickly process existing files:

 
     cd dest_dir                    # The destination directory 
     foreach f ( ../src_dir/*.a )   # ../src_dir is the source directory
       gnatchop $f
     end
gnatchop will show you what sources are causing problems.

(Up to Table of Contents)

9.3: I hear that Ada is slower than Fortran or C, is that true?

First, note that you are comparing compilers, not languages. There is no such thing as "fast" Ada code any more than there is "fast" C++ or Fortran code. Now, when comparing execution speeds on similar platforms, you must keep in mind the optimization levels, OS tuning, etc. while making the comparisons. The bottom line is that benchmarking, especially between two different languages, requires _very_ careful measurement. In general, such results should be viewed with caution.

(A message from Bevin Brett of DEC)

I have been asked to comment on the relative performance of algorithms coded in Ada and in Fortran.

This question has come up repeatedly over the years, and deserves a complete answer, rather than a simplistic one.

There are many factors which influence the size and execution speed of the running program, and they all play together to get a full answer. I shall then discuss an exact Ada v. Fortran comparison that Digital was involved in.

First, a position statement: The variation between Ada and Fortran is less than the variation within the language caused by the exact implementation details. A person versed in the Ada issues should do as well in Ada as a person versed in the Fortran issues will do in Fortran. The size and execution speed of the result should be within a few percent of each other.

(a) Differences due to the compiler

(b) Differences due to the language

(c) Differences due to the bindings

(d) Differences due to the author

The Ada Performance Benchmark

A DEC Ada customer had a Fortran benchmark that had been translated into Ada without awareness of the above issues, and was running substantially slower with DEC Ada than the original was with DEC Fortran.

Bevin Brett, a DEC Ada team member, developed the above guidelines in the process of retranslating the code into Ada.

Portions of this translation are shown here (a) as an illustration of the application of the above rules, and (b) as an illustration of the kind of operations that were present in the benchmark.

The whole benchmark has not been provided to avoid possible issues of ownership.

The resulting Ada benchmark components each ran within a few percent of their Fortran counterparts. The Ada code is available by FTP, in file ftp://ftp.adahome.com/pub/FAQ/ada-vs-fortran.ada

(Up to Table of Contents)

9.4: Isn't Ada less "elegant" than Eiffel?

While it is true that programming-language support for "assertions" is an important contribution of Eiffel to software construction, this is not an issue of "elegance", and there are many other important factors to consider.

Note also that preconditions and postconditions can be fairly easily and efficiently included in Ada code. Invariants seem difficult to emulate directly in Ada. If you're really interested in the formal use of assertions with Ada, maybe Anna is a solution for you.

(Tucker Taft comments)

I guess one thing that bothers me a little is that people are quick to say that Eiffel is "elegant" without really looking at it. I fear that such statements will become self-fulfilling prophecies, with those programmers interested in elegance migrating over to Eiffel rather than sticking with Ada.

In particular, although I like the assertion stuff in Eiffel, I think the language has a number of "inelegant" aspects. For example:

  1. exception handlers only at the top level of a routine, with the only way to "handle" an exception being by retrying the whole routine.

  2. No way to return from a routine in the middle. This makes it a pain in the neck to search through a list for something in a loop, and then return immediately when you find what you want. (I have never found the addition of extra boolean control variable a help to the understanding of an algorithm.)

  3. Namespace control handled by a separate sublanguage, and no real higher level concept of "module" or "subsystem."

  4. An obscure notation like "!!" being used for an important and frequent operation (construction).

  5. No way to conveniently "use" another abstraction without inheriting from it.

  6. No strong distinctions between integer types used for array indexing.

  7. Using the same operator ":=" for both (aliasing) pointer assignment, and for value assignment, depending on whether the type is "expanded." (Simula's solution was far preferable, IMHO).

    And most critically:

  8. No separate interface for an abstraction. You can view a interface by running a tool, but this misses completely the importance of having a physical module that represents the interface, and acts as a contract between the specifier or user of an abstraction and its implementor. In Eiffel, one might not even be truly aware when one is changing the interface to an abstraction, because there is no particular physical separation between interface and implementation.

I consider many of the above problems quite serious, with some of them being real throwbacks to the old style of programming languages where there were no well defined interfaces or modules.

Hence, I cringe a bit when people say that Eiffel is the "most elegant" OOP and that they would use it if only it were practical to do so. In many ways, I think Ada is much better human-engineered than Eiffel, with important things like range constraints built into the language in a way that makes them convenient to use. Although general assertions are nice, they don't give you the kind of line-by-line consistency checks that Ada can give you.

To summarize --
Although Eiffel certainly has a number of nice features, I don't consider it ready for prime time as far as building and maintaining large systems with large numbers of programmers. And from a human engineering point of view, I think Ada is significantly better.

(Up to Table of Contents)

9.5: Are there any papers detailing the differences between Ada and C++?

Below are two references. Bear in mind that it is difficult to make such a comparison without exposing biases. However, the two papers below are well worth reading.

"A Comparison of the OO features of Ada9x and C++" in Springer Lecture Notes in CS: "Ada Europe 93" pp.125-141 (short paper, good reading, enlightens idioms)

ftp ajpo.sei.cmu.edu in directory: /public/ada9x, document: 9x_cplus.hlp

(Up to Table of Contents)

9.6: I keep hearing that Ada is a "strongly typed language", but it seems different from what's meant in C++. Are they different?

(Tucker Taft responds)

I certainly agree that ANSI C and C++ are statically typed languages, but I would debate the "strength" of their typing.

Essentially any support for implicit conversion (implicit "casting," "promotion", "usual" arithmetic conversions, etc.) "weakens" a type system (but also makes it "friendlier" in some ways).

C allows implicit conversion between all integer types and all enumeration types. C++ at least cuts off implicit conversion to enumeration types, but retains implicit conversion among all integer (and floating-point) types. Also, in both C and C++, typedefs for pointer/array types are essentially "macros"; all pointer types with the same target type are implicitly interconvertible.

Finally C++ allows the user to define a number of their own implicit conversion operators, which basically allows the user to "weaken" the type system as they see fit.

Of course, all of this implicit conversion serves a purpose, but it does tend to move C/C++ toward the "weaker" end of the weak vs. strong typing spectrum.

Note that the "strong" distinctions between integer types helps dramatically in catching (at compile-time) array indexing errors in Ada programs, by making sure that if you have an array indexed by a count of apples, you don't index into it with a count of oranges (without an *explicit* conversion). The advantages of "strongly" distinguishing enumeration types is even more obvious (and the designers of C++ recognized this).

The strong distinctions between access types (pointer types) in Ada also has advantages, allowing access types to be represented as offsets within their storage pool rather than as addresses, and giving more high-level control over storage management.

Strong typing can be carried too far, and some amount of implicit conversion is essential to make OOP palatable. But note that in Ada 95, even with OOP, we don't allow implicit conversions that truncate the extension part of a record (this is a relatively common mistake in C++ when passing parameters by value). Instead, in Ada 95, the language distinguishes between a specific type T and the class-wide type T'Class, and allows implicit conversions to T'Class from T or any of its derivatives, but not to the specific type T. Conversions to the class-wide type never implicitly truncate the extension part. Conversions to a specific type can truncate, and hence must be explicit.

Note also that in Ada there are three distinct kinds of conversions, implicit ones, explicit ones, and unchecked ones. Only the unchecked ones are potentially unsafe. The explicit ones are safe, with either compile-time or run-time checks to ensure that. In C there are only implicit and explicit/unchecked conversions. C++ has recently added a checked, explicit "dynamic" cast, but still it will be common to use "normal" explicit casts for both checked and unchecked conversions, thereby making it more difficult to identify places where the type system might be compromised.

Hence, the bottom line is that the type checking is (objectively) "stronger" in Ada than C/C++, though that doesn't necessarily mean "better" -- whether one is "better" for a particular style of programming than the other is a "religious" issue IMHO. I know my religion currently favors the stronger checking of Ada in most cases [except perhaps for multiply/divide, where I personally believe the checking should either be weaker, or directly support the concept of "units"/"dimensions"].

(Up to Table of Contents)

9.7: I'm told Ada does all sorts of static type checking, but can't you get the same effect using a tool like "lint" with C?

No, here are a few reasons why (this list is by no means complete):

(Submitted by Norm Cohen)

Of course even stronger arguments can be made about Ada's RUN-TIME checks (which can be used with little additional overhead because the information contained in an Ada program and the knowledge that the program has passed compile-time consistency checks make it possible to optimize away the majority of the checks). These checks, which are absent in C, tend to smoke out errors early by detecting internal inconsistencies that might not otherwise be detected during testing. This reduces the likelihood of fielding a system that appears to work well during testing but fails in operational use.

(Up to Table of Contents)

9.8: Does Ada have something like the Standard Template Library (STL) in C++, or components like you find in Smalltalk environments?

Yes, in various ways.

Few components are part of the ISO standard. Ada 95 has an expanded set of predefined library units, covering e.g. strings of varying- or dynamic-length, elementary numerical functions, random number generators, complex numbers, and more; in addition, the Special Needs Annexes standardize many advanced services which have commonly been provided by separate components in the past.

A lot of free Ada software components are available from anonymous FTP sites. There is also an upcoming release of the Booch Components for Ada 95 under the GNU Library General Public License (LGPL); this will give you the ability to freely include the library components in your application without any cost or obligation. (Contact dweller@dfw.net for more details.)

What about STL and the Smalltalk library?

The C++ STL doesn't contain much. Really. Breaking its source code down, it contains less than 3000 semicolons (~7000 total lines). The entire library exists in C++ header files as inlineable code (supposedly within a few percent of the efficiency of hand-optimized code).

STL class hierarchy

     bool, heap     -- of course Ada does not need a bool class!
      \ function, pair, stack
         \ iterator, tempbuf, projection
            \ algobase
               \ algorithms, bitvector, deque, list, tree, vector
                  \ map, multimap, set, multiset

The main author of the library, Alexander Stepanov, created the Ada Generic Library in the 1980's -- and later borrowed from this to create STL. (There is an interview with Stepanov in the March 1995 issue of Dr. Dobb's Journal -- in the C Programming column beginning on page 115. Stepanov explains that these components were first done in Ada.)

The Smalltalk library is quite eclectic. It covers everything from Boolean to bitmaps, dictionaries and other collections. Parts of it have direct equivalents in Ada 95, parts are already available in components from anonymous FTP sites and/or will be in the Booch Ada 95 components, and other parts are available from commercial Ada component suppliers.

(Up to Table of Contents)

9.9: Where can I find the equivalent of "printf" in Ada?

While the standard package Text_IO provides many features, the request for a printf-like function is not unusual.

(solution based on a suggestion by Tucker Taft)

It is possible to produce a printf-like capability by overloading the "&" operator to take an object of type Format and an object of some type and return the Format, properly advanced, after having performed the appropriate output. The remaining format can be converted back to a string--e.g. to examine what is left at the end of the format string-- or simply printed to display whatever remains at the end. For example:

     with Text_IO;
     package Formatted_Output is
       type Format is
         limited private;

       function Fmt (Str : String)
         return Format;

       function "&" (Left : Format; Right : Integer)
         return Format;
       function "&" (Left : Format; Right : Float)
         return Format;
       function "&" (Left : Format; Right : String)
         return Format;
       ... -- other overloadings of "&"

       procedure Print (Fmt : Format);

       function To_String (Fmt : Format)
         return String;

     private
       ...
     end Formatted_Output;

     with Formatted_Output; use Formatted_Output;
     procedure Test is
       X, Y : Float;
     begin
       Print (Fmt("%d * %d = %d\n") & X & Y & X*Y);
     end Test;
The private part and body of Formatted_Output are left as an exercise for the reader ;-).

A "File : File_Type" parameter could be added to an overloading of Fmt if desired (to create something analogous to fprintf).

This capability is analogous to that provided by the "<<" stream operator of C++.

(Up to Table of Contents)


10: Interfacing with Ada

10.1: I am writing software that used the Distributed Interactive Simulation (DIS) interface, does an interface exist in Ada?

Yes. DIS is a standard for communications between simulators using an Internet Protocol network (IP). DIS provides a unified virtual environment for multiple simulator users on a network. It is used mostly in the DoD simulations business, but it is applicable to ANY simulation. It is an industry initiative involving military training and procurement organisations, simulator vendors and universities mostly in the US, but the technology is unclassified.

The US Army is funding quite a bit of DIS research and development. The Institute of Simulation and Training, URL http://www.tiig.ist.ucf.edu/ is a center at the University of Central Florida (UCF) which serves as the support contractor for the Simulation and Training Command (STRICOM), URL http://www.stricom.army.mil/; STRICOM has information about the PM DIS (project manager for DIS) and the DIS development roadmap. Current (published) standards can be found at UCF IST, as are BBS's for the DIS working groups who are attempting to push those standards forward. The BBS contains an Ada binding for DIS.

Note that the above provides a thin binding to C code. It may be worthwhile to take the time to make high-level DIS bindings. Ted Dennison, dennison@escmail.orl.mmc.com reports having done it (while working for what is now Lockheed Martin Simulation Systems) in over 2 man-months using an experienced Ada engineer, and that it was well worth it. Many bugs were found in the C DIS code of the machine they were networked with. "A strongly-typed interface is the network programmer's best friend."

At TRI-Ada'94 there was a demonstration by Coleman Research Corporation (CRC); here's their short pitch: "CRC presents Ada VR-Link, the first commercially available DIS NIV. It provides all of the facilities necessary to jump start your DIS compliant simulation development efforts. For more information call (205) 922-6000."

Also, the AJPO sponsored an Ada Technology Insertion Program (ATIP) relating to this: FY93 ATIP project 17, titled "Ada Distributed Interactive Simulation (ADIS)". Available from directory ftp://sw-eng.falls-church.va.us/public/AdaIC/source-code/bindings/ADIS-bindings

The Ada Distributed Interactive Simulation (ADIS) provides an Ada interface to the IEEE 1278 Distributed Interactive Simulation (DIS) protocols. The project was developed in Ada 83 (MIL-STD-1815), on Silicon Graphics Indigo R4000 machines using Verdix Ada 6.2.1 under the IRIX operating system, version 5.2. The Graphical User Interfaces (GUIs) were developed for X Window version X11R5 using Motif 1.2.

There are several sources of information available on DIS itself. The IEEE version of the DIS standard is available through (and only through) the IEEE (std IEEE 1278). Draft versions of the standard are available from the Institute for Simulation and Training at the University of Central Florida. They take orders at (407) 855-0881, and questions (about ordering) at (407) 658-5054.

(Up to Table of Contents)

10.2: Is there any support for Common Object Request Broker Architecture (CORBA) for Ada 95?

OC Systems sells a CORBA Ada product; it is "Standard equipment" with their PowerAda compiler. Rational, and OIS are also planning on selling CORBA products for Ada.

Objective Interface Systems, Inc. (OIS), MITRE, and DISA have been working on a mapping from CORBA IDL to Ada 95 for about six months. Bill Beckwith (Bill.Beckwith@ois.com) will send a recent copy of the mapping document to any interested parties.

Note that CORBA IDL to Ada 95 mapping specifies a mapping, not a binding. This will put Ada 95 on equal footing with the C++ and Smalltalk products. (except that, of course, the Ada mapping is cleaner ;-).

(Up to Table of Contents)


11: Finding Additional Information

11.1: Where can I find Ada books?

The Ada 95 book reviews are at the Ada Home, URL http://www.adahome.com/Resources/Books/ada95reviews.html

Look also at the companion comp.lang.ada FAQ or the book section of the Ada Home (HBAP) WWW Server, URL http://www.adahome.com/Resources/Books/

(Up to Table of Contents)

11.2: Are there other Ada-related FAQs?

Yes. They all appear in comp.lang.ada at regular intervals: comp.lang.ada FAQ, learning Ada FAQ, public Ada library FAQ, and Ada WWW server FAQ. All these are permanently available in hypertext format from the Ada Home (see below) and in ASCII format from ftp://ftp.adahome.com/FAQ

(Up to Table of Contents)

11.3: What is the "Ada Home" (HBAP) WWW Server?

The Home of the Brave Ada Programmers (HBAP) WWW Server is alive and heavily used. It is a hypertext, multimedia information server for the Ada programming language.

The URL of the HBAP WWW Server is

	http://www.adahome.com/

The Ada Home provides Ada-related information and hypertext access in areas including:

For instance, you will find a list of schools using Ada in CS1 or CS2, an article on commercial success stories, information about software components, as well as hypertext versions of the Ada reference manual (both 95 and 83).

The Ada Home keeps growing. All comments, ideas, and requests for additions or corrections, are welcome (e-mail to the webmaster).

(Up to Table of Contents)


12: Pretty-printing and Measuring Ada Source Code

12.1: Is there software that generates a pretty PostScript file from Ada source code?

Pretty Ada code in PostScript means that e.g. reserved words are in bold and comments are in italics. This is a separate issue from re-formatting and automatic indenting.

Auburn University has been working on a project called GRASP, located at http://www.eng.auburn.edu/department/cse/research/grasp/ which is something of an IDE for Ada and other languages. It does excellent Postscript printing of Ada code.

If you use the new Ada Mode for GNU Emacs (available from ftp://cs.nyu.edu/pub/gnat), go and get the package ps-print.el from any emacs archive (e.g. in directory ftp://archive.cis.ohio-state.edu/pub/gnu/emacs/elisp-archive). With this package you can print your code as you see it on the screen, say with bold keywords and italic comments.

Another possibility is to feed the source to "vgrind" (see below), then pipe the result through "pscat" (to get PostScript) or "lpr -t" (to print), e.g.:

     vgrind -d <vgrind_defs_file> -lada -o1- -t -w $* | lpr -t

(Up to Table of Contents)

12.2: I use vgrind to do "pretty printing" of my source. Is there a vgrind definition for Ada?

# Ada!
ada|Ada:\
	:pb=(^\d?(procedure|function|package|package body))\d\p:\
	:bb=if|case|begin|loop:be=end:\
	:cb=--:ce=$:\
	:sb=":se=":\
	:lb=':le=':\
	:id=_.:\
	:oc:\
	:kw=abort abs abstract accept access aliased all and array at\
	begin body case constant declare delay delta digits do else\
	elsif end entry exception exit for function generic goto if in is\
	limited loop mod new not null of or others out package pragma\
	private procedure protected raise range record rem renames requeue\
	return reverse select separate subtype tagged task terminate then\
	type until use when while with xor:

Note that the above has a problem with attributes, because the "lb" and "le" terms make two-attributes-20-lines-apart look like one "string literal." Ada 95 keywords are recognized.

Here is another definition, which "fixes" this problem (not perfect, but probably better). Only Ada 83 keywords are recognized.

 # In order to get the ticks to work, we are assuming that there will be
 # whitespace before a literal (like '"') and *not* when used for an
 # attribute (like 'Length).
 # For sb/se, we are ALSO assuming that literals have whitespace before/after.
Ada|ada:\
	:pb=^\d?(procedure|function|package|package\dbody)\d\p:\
	:bb=begin:be=end:\
	:cb=--:ce=$:\
	:sb=( |\t|\()":se="( |\t|;|,|\)):\
	:lb=(>| |\t)':le='(\)| |\t|;):\
	:tl:\
	:oc:\
	:kw=abort abs accept access all and array at begin body case constant\
	declare delay delta digits do else elsif end entry exception exit for\
	function generic goto if in is limited loop mod new not null of or\
	others out package pragma private procedure raise range record rem\
	renames return reverse select separate subtype task terminate then\
	type use when while with xor:

(Up to Table of Contents)

12.3: How about a source code reformatter?

If you can run a Perl script (Perl is freely available for almost every OS in the world), you can use the program aimap, written by Tom Quiggle of SGI. aimap is not really a pretty printer, since it only changes the case of identifiers and reserved words (according to the options set). It can be found at http://reality.sgi.com/employees/quiggle_engr/aimap

(Up to Table of Contents)

12.4: How can I count source lines of code (SLOC)?

Under Unix and many operating systems (apparently even MS-DOS), the following works well:
     wc -l file_name

If you only want to count "statement lines" (lines with semicolons), use

     sed 's/--.*$//' file_name | grep ';' | wc -l

Some versions of grep have a '-c' option to print a count of the matching lines, but this may not be universal. You can fold the grepping into the sed command but that's not as readable:

     sed -n -e 's/--.*$//' -e '/;/p' file_name | wc -l

Please note that measuring SLOC should be used to indicate an approximate relationship to the size of other projects, and as such, provided that there is some uniformity in the form and number of comments, it does not matter whether comments are counted or not. It has often been observed that there is a very high correlation between measurements of SLOC, semicolons, and Halstead bits (there is probably also a high enough correlation with the number of characters).

With VMS, try the following, which will print out the number of lines ("records") and characters (use ";" instead of "~~~~~" to count lines with semicolons; note that "records" will match even in comments):

     $ search/stat file_name.ada "~~~~~"

(Up to Table of Contents)

12.5: Can I measure other things?

There is ASAP, the Ada Static Analyzer Program, written in Ada and set up to compile under Dec Ada on a Vax running VMS. Gives SLOC, McCabe's, and more. It is available via anonymous ftp in directory ftp://ftp.sei.cmu.edu/pub/dd


13: Credits

The first draft was made by Dave Weller. The following persons have contributed (directly or indirectly, intentionally or unintentionally, through e.g. comp.lang.ada) to the information gathered in this FAQ: Tucker Taft, Dave Weller, David Arno, Christine Ausnit, Bill Beckwith, Moti Ben-Ari, Chip Bennett, Bevin Brett, David Bulman, G. Vincent Castellano, Franklin Chen, Norm Cohen, Marin David Condic, John Cosby, Richard Crutchfield, Theodore E. Dennison, Robert Dewar, Bob Duff, Robert Eachus, Rolf Ebert, Dave Emery, Mitch Gart, Victor Giddings, Jeffrey L. Grover, Laurent Guerby, Richard G. Hash, Matthew Heaney, Fergus Henderson, Niklas Holsti, Job Honig, Jean D. Ichbiah, Nasser Kettani, Wayne R. Lawton, Robert Martin, Robb Nebbe, Jonathan Parker, Isaac Pentinmaki, Bruce Petrick, Paul Pukite, Richard Riehle, Keith Shillington, David Shochat, André Spiegel, Keith Thompson, Joyce Tokar, Kevin Weise, David A. Wheeler, Fraser Wilson, and the maintainer has simply :-) organized, polished, or added some information for your satisfaction. The general HTML structure of this FAQ was originally inspired by the (now differently structured) WWW FAQ.

(Up to Table of Contents)


14: Copying this FAQ

This FAQ is Copyright © 1994-1998 by Magnus Kempe. It may be freely redistributed --as posted by the copyright holder in comp.lang.ada-- in other forums than Usenet News as long as it is completely unmodified and that no attempt is made to restrict any recipient from redistributing it on the same terms. It may not be sold or incorporated into commercial documents without the explicit written permission of the copyright holder.

Permission is granted for this document to be made available under the same conditions for file transfer from sites offering unrestricted file transfer on the Internet and from Forums on e.g. Compuserve and Bix.

This document is provided as is, without any warranty.

(Up to Table of Contents)


Magnus Kempe -- M.Kempe@ieee.org