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, 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.
What's important and missing:
- everything, life, and 42 :-)
(Up to Table of Contents)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
(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:
- simple to implement
- simple to describe informally
- simple to describe formally
- results in simple programs
- simple to understand and/or remember
- short to describe
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)
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)
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)
(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:
- a language construct, that encapsulates the definitions
of data members, member functions, nested types, etc.;
- a particular kind of type, defined by a class construct
(or by "struct" which is a special case of "class");
- a set of types consisting of a type and all of its derivatives,
direct and indirect.
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:
- a "package" encapsulates the definitions of types,
objects, operations, exceptions, etc which are logically
related. (The operations of a type defined immediately within
the package where the type is declared are called, in Ada 95,
the "primitive operations" of the type, and in some sense,
define the "primitive" semantics of the type, especially if
it is a private type.)
- a "type" is characterized by a set of values and a set of
primitive operations (there are a million definitions of
"type," unfortunately, but you know what I mean...);
- a "class" is a set of types with similar values and operations;
in particular, a type and and all of its derivatives, direct and
indirect, represents a (derivation) class. Also, the set of
integer types form the integer "class," and so on for the other
language-defined classes of types in the language.
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)
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)
This answer intentionally left blank.
(Up to Table of Contents)
There is a lengthy paper in file
http://archive.adaic.com/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:
- If the same type appears as an ancestor more than once,
should all or some of its data components be duplicated, or shared?
If any are duplicated, how are they referenced unambiguously?
- If the same-named (including same parameter/result profile) operation
is inherited along two paths, how is the ambiguity resolved?
Can you override each with different code? How do you refer
to them later?
- Etc.
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)
(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)
(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)
(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)
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)
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)
-
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)
(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)
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)
(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)
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)
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)
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)
(Up to Table of Contents)
The general answer is: use controlled types
(RM95-7.6).
For detailed explanations, read the following papers:
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)
(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)
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)
(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)
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)
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)
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)
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:
- Pascal to Ada:
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.
- Fortran to Ada: ???
- COBOL to Ada: ???
- C++ to Ada: ???
- C to Ada: ???
- Modula-2 to Ada:
(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.
- Visual Basic to Ada: NOT! :-)
(Up to Table of Contents)
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)
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
In the case of the DEC Ada and Fortran compilers, the optimizer and
code generator are the same. Never-the-less, the exact inputs into
the optimizer and code generator may differ slightly when the same
algorithm is compiled by the Ada and Fortran compilers, and this can
result in major differences in the generated code. In these cases the
compiler front ends can usually be modified to correct the slower one.
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
It is very important that the same algorithm be written in the two
languages. The biggest differences we have observed are
- Having the wrong dimension varying fastest, since it is
desireable to have the first dimension changing fastest
in Fortran, and the last dimension in Ada. Thus when
an algorithm is transliterated, the array indexes must be
reversed.
- Using compile-time-known bounds for arrays in Fortran, and
using unconstrained arrays in the Ada code. Knowing the
exact values of the dimensions at compile-time results in
much better code.
- Not suppressing all the runtime checks in Ada. The Fortran
compiler assumes all array bounds are in range, and all
arithmetic operations do not overflow. You must use a
pragma Suppress to tell this to the Ada compiler as well.
- Don't use arrays of Ada Booleans to match arrays of Fortran
Integers, because accessing bytes on a RISC system might
be much worse than accessing fullwords.
(c) Differences due to the bindings
The biggest bindings differences are related to Fortran's built-in
support for complex types, and for various math routines such as
SQRT and SIN, compared with Ada code that often uses hand-coded or
ISO standardised versions of these functions with different requirements
than are imposed on the Fortran versions.
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
The use of good Ada and Fortran style can also effect the generated
code. Provided the author writes in good Ada style, and follows the
above guidelines, the generated code should do as well as Fortran.
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)
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:
- exception handlers only at the top level of a routine,
with the only way to "handle" an exception being by retrying
the whole routine.
- 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.)
- Namespace control handled by a separate sublanguage, and
no real higher level concept of "module" or "subsystem."
- An obscure notation like "!!" being used for an important
and frequent operation (construction).
- No way to conveniently "use" another abstraction without
inheriting from it.
- No strong distinctions between integer types used for
array indexing.
- 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:
- 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)
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)
(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)
No, here are a few reasons why (this list is by no means complete):
(Submitted by Norm Cohen)
- Running both Lint and a C compiler requires the program text to be
parsed and semantically analyzed twice. The results of an Ada
compiler's parse and semantic analysis are used directly in performing
consistency checks.
- The rules of Ada provide the opportunity for stronger consistency
checks than are possible with C. For example, an Ada programmer can
declare distinct integer types to represent distinct abstractions. An
Ada compiler will catch an inadvertent intermixing of these two types,
but there is no way a corresponding distinction can be made in C, so
there is no way for Lint to perform a corresponding check. Similarly,
in C, a pointer to an object of type T is indistinguishable from an
array of objects of type T.
- The rules of the Ada language ensure that the program text provides
information allowing PRECISE consistency checks. For example, the
expression in an Ada case statement can be written to have a static
subtype, allowing the compiler to ascertain that all possible values
have been covered without resorting to a default (when others) arm.
- With lack of precise information, Lint has no choice but to be overly
pessimistic or, with different settings for a complicated set of
options, overly optimistic. When it is overly pessimistic, the user
sees too many "false alarms" and may end up ignoring valid warnings.
When it is overly optimistic, Lint overlooks certain errors.
- It is impossible to forget to run consistency checks when using an Ada
compiler. (Of course a C programming environment could be set up so
that the C compiler could only be invoked from a script that also
invokes Lint.)
- A compilation that fails Ada consistency checks is rejected. A
compilation that fails Lint consistency checks may still be compiled,
and its object file used (intentionally or accidently) in building the
system. (One cannot automate the rejection of programs that fail Lint
unless one is certain that there will never be any false warnings.)
- Ada enforces consistency among separately compiled units.
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)
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)
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)
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)
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)
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)
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/pub/FAQ
(Up to Table of Contents)
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:
- Historical notes on Ada
- References
- Ada FAQs
- Ada 95
- Tutorials
- Standards
- Bindings
- Tools and Components
- Intellectual Ammunition
- Introductory Material
- Resources
- CS Technical Reports
- FTP and WWW Sites--including mirror sites
- Calendar of Ada-related events
- Ada Today
- Frequently Asked Questions--with Answers (from comp.lang.ada)
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
webmaster@adahome.com).
(Up to Table of Contents)
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)
# 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)
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)
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)
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
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)
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)
|