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.
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.
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
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 :-).
What's important and missing:
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.
-- 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 );
On other Ada compilers, you must set an environment variable flag in order to cause the exception information trace to be displayed.
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;
A : Integer := Integer'(1); -- this is a qualifier: same as ":= 1;" B : Integer := Integer (1); -- this is a conversion(Up to Table of Contents)
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;
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.
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;
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.
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.
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
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
(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.
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.comThe 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!
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.
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.
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.
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.
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.
What is exactly the difference betweentype A is access Object'Class;andtype 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.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;
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.
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.)
(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.)
(See subclause A.5.1 of the Ada 95 RM, and Part III, Section A.3 of the Ada 95 Rationale.)
(See subclause A.5.2 of the Ada 95 Reference Manual, and part III, section A.3.2 of the Ada Rationale.)
(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).
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.
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.
For detailed explanations, read the following papers:
(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.
(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:
attributes: weight, age, timeline for amount consumed
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.
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.
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.
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:
R.R. Software's Pastran program (Pascal to Ada Translator).
To see the differences in programming style, see "Ada for Experienced Programmers", by A. Nico Habermann and Dewayne E. Perry (Addison-Wesley Pub. Co., Reading, Mass., 1983). Covers Ada and Pascal.
(from Wayne R. Lawton)
The Idaho National Engineering Laboratory (INEL), a Dept of Energy Lab has
a basic capability for Modula-2 to Ada-83. The tool is "research grade"
quality, but may provide a starting point for what you need. This is the
same group of people who brought you AdaSAGE. Give them a ring at (208)
526-0656. This is an answer desk hotline in the section that wrote the
tool.
If you are looking for commercial quality, I wish you the best of luck. If you just need something to perform 80% of the grunt code translation, I think this might meet your needs. I know of two systems comprising about 250,000 lines of code that were originally developed in Modula-2 then translated and cleaned up in Ada 83 after Alsys 1.0 for the PC came out back around 1987.
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 endgnatchop 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
We have not observed any major differences in generated code quality between the DEC Ada and DEC Fortran compilers caused by such issues.
(b) Differences due to the language
(c) Differences due to the bindings
DEC Ada has built-in support for complex types, and also has bindings directly to the same primitives that Fortran uses for its math routines and so gets the same performance as Fortran does.
(d) Differences due to the author
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:
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.)
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++.
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.
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 ;-).
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/
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).
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:
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
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.