In the 1970s, there were 450 languages in use for DoD projects which involved embedded systems (these are computers which are part of another device, such as the electronic ignition modules in cars). The languages were not at all standardized. Defense contractors could make up a new language for every contract they worked on. Very little software was reused, and few tools (such as debuggers) were created. Maintaining software for the equipment was a monumental task.
In 1974, the DoD proposed that a standard high-level language for embedded systems should be developed. A document describing what features the language would have to have was written, and then comments were solicited from many organizations. The document was revised, and there was a request for proposals to design a language that would meet the specifications.
Eventually, four groups were chosen to develop a language at the same time. All of these groups had been influenced by Pascal, and programmers who know both Ada and Pascal can easily see the similarities. After an initial evaluation, two were eliminated. Another round of development and evaluation followed, and finally a winner was selected. The language was named Ada, after Augusta Ada Byron, Countess of Lovelace, who is sometimes regarded as the world's first programmer because of the work she did with Charles Babbage.
The language design and rationale was published, and after several more years of refinement, the 1983 version of Ada was standardized, and the design was to remain unchanged for five years. In 1988, a revision effort began which required support for (among other things) object-oriented programming, more flexible libraries, and better control mechanisms for shared data. The result of this was Ada95.
Ada never caught on widely because early compilers were expensive and sometimes didn't work very well. Additionally, the language is large and complex -- it has been jokingly said that `Ada is everybody's six favorite languages'.
The Hacker's Dictionary, to take one example, describes Ada as `difficult to use', and that `the kindest thing that has been said about it is that there is probably a good small language screaming to get out from inside its vast, elephantine bulk.'
Some Ada programmers object to this description, but it's not unfair if one considers the Hacker's Dictionary definition of `hacker'. It includes `one who enjoys the intellectual challenge of creatively overcoming or circumventing limitations' and `a person who is good at programming quickly'. If circumventing limitations and knocking out code in a hurry is what you like to do, Ada is not going to be your favorite language.
But not every program is a hack; not every programmer wants to get around limitations; sometimes, you should keep your creativity to yourself. Consider the space shuttle, for example. If you were getting on board, and you found out that the software which ran the life-support systems was `creative', and had been `written quickly' by someone who likes to `circumvent limitations', would that make you happy? One of my limitations is that I can only go about three minutes without air. I don't want anyone being creative and trying to circumvent that.
Also, consider that the shuttle's on-board systems include 17million lines of code. You can't write 17million lines of code quickly. You can't write 17million lines of code at all. You probably couldn't read that much code if you had 10 years and nothing else to do.
It takes lots of people working together to produce something like that. In such a case, a language which is very strict about what you're allowed to do makes it more likely that discrepancies and inconsistencies will be discovered by the compiler, so that the thousands of different modules will work when they are all put together.
Among people who write code that has to work, Ada is the language of choice. It has features which help ensure reliability that more common languages such as C++ and Perl do not have -- and it actively omits some features which lead to bad coding. We'll talk about some of each.
Here's the standard Hello World program:
with Ada.Text_IO;
procedure Hello_World is
begin
Ada.Text_IO.Put("Hello World!");
end Hello_World;
`with' is approximately equal to `#include': here, it means that you want to work with the functions available in the `Ada.Text_IO' package. The rest is pretty straightforward: you want to put out the text `Hello World', and the Put function you are interested in is the one in Ada.Text_IO.
For functions which you use a lot, you can say this:
with Ada.Text_IO; use Ada.Text_IO;
[...]
Put("Hello World!");
The `use' keyword specifies that a function name which cannot be
resolved locally should be searched for in this library. Some Ada
experts recommend avoiding this; others suggest it should be used
sparingly.
Note that unlike C or C++ or Perl, everything here ends with a semicolon: the with statement, even the `end' has to have a semicolon, because the `end Hello_World' is really the end of the compound statment that started with the `procedure' -- and compound statements have to have semicolons at the end.
One advantage to Ada is strong typing. Here's a standard hunk of code from many CS&P pop-quizzes:
double foo;
foo = 1 / 3;
What's foo? At least some students will say .33333. Never fails.
Foo is zero, because the integers give an integer division and the
result of the division is zero. The value isn't converted to a double
until the assignment, by which point the decimal portion has
been discarded. The compiler will not warn you about it. In Ada, the
comparable lines of code will not compile at all. The result of the
division is an integer, and the variable is not, and so the assignment is
invalid. If you mean for the value to be converted, you have to say
so explicitly.
That sort of strong typing is built into the language; but you can take advantage of it in other ways. For example, in C or C++, one standard way to write some code may be like this:
int year, month, day, hour, minute, second;
And if you later botch something, such as here:
day = minute;
The compiler won't tell you that you've done anything wrong. At
runtime, the program will probably not crash. But if minute was
set to 37, and you're working with April, it may end up telling you
about May 7th.
In Ada, the compiler is much pickier about what you can do, and it checks everything to see if you've done anything illegal. To prevent bugs, you can take advantage of these features, declaring the variables (and assigning initial values) this way:
subtype Year_Type is Integer range 1582..4000;
subtype Month_Type is Integer range 1..12;
subtype Day_Type is Integer range 1..31;
subtype Hour_Type is Integer range 0..23;
subtype Minute_Type is Integer range 0..59;
subtype Second_Type is Integer range 0..59;
Year : Year_Type := 2001;
Month : Month_Type := 9;
Day : Day_Type := 19;
Hour : Hour_Type := 12;
Minute : Minute_Type := 0;
Second : Second_Type := 0;
It's a lot longer; but if you try to do `Day := Minute', the compiler
won't let you. Those two variables are different types. You can
do the assignment if you specifically say that you are doing so, by
naming the type you want the value converted to:
day := Day_Type(Minute);
Further, if at any time any of the variables is assigned a value outside
the specified range for the type, the runtime environment will raise an
exception. If you haven't written code to handle the exception, the
program will be halted. So if we get to the line with the conversion, and
`Minute' is 37, the computer will catch the error. If we've written code
that handles mistakes, great. If not, the program stops. So this is much
longer than the C version, and it requires a lot more planning to use well.
But programs written this way have many fewer bugs. In the long run,
some companies have found that while the time to write the initial program
in Ada is longer than in C or C++, the time spent on debugging is much
shorter. In some cases, so much shorter that it more than makes up for
the time spent originally designing the program.
Exception handling is when you specify what to do when something goes wrong. For example, consider this program:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Numerics; use Ada.Numerics;
with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;
procedure Square_Root is
begin
if Argument_Count < 1 then
Put("Usage: ");
Put(Command_Name);
Put(" decimal");
Set_Exit_Status(Failure);
else
Put( Item => Sqrt(Float'Value(Argument(1))), Exp => 0);
Set_Exit_Status(Success);
end if;
exception
when Constraint_Error =>
Put(Command_Name);
Put(": cannot interpret numerical value from """);
Put(Argument(1));
Put("""");
Set_Exit_Status(Failure);
when Argument_Error =>
Put(Command_Name);
Put(": cannot take square root of ");
Put(Argument(1));
Set_Exit_Status(Failure);
end Square_Root;
If the Sqrt function is passed a negative number, it will raise an
Argument_Error; the result will be that the program will jump
to the section where that exception is named. If the argument is
something like `a', then Float'Value will raise a Constraint_Error,
and execution will jump to that part of the code.
In C++, you have to put the code which does things inside a `try' block, and then have a `catch' block for exceptions. In the event there's a problem, you have to `throw' the exception yourself, or call a function which throws an exception. (I don't know if there is an automatic exception for division by zero, but none of the books I looked in mentioned it.)
try {
if (atof(argv[1]) < 0)
throw 1;
cout << sqrt(argv[1]);
}
catch (int e) {
if (e == 1)
cout << "square root of a negative\n";
}
In Ada, any out of range value for any variable throws an exception; in C++, to get the same result, you'd have to have dozens or hundreds of if statements checking each variable and throwing exceptions when something goes wrong.
I call your attention to this because Ada's high-reliability doesn't result just from exception-handling, or from strong typing with range limits: the real power is because Ada has both features, and they can be used in combination. If the program just crashes when someone enters an invalid value, that's not very useful; you need exception handling to really take advantage of ranges. But if you have to write out statements to check the value of every variable entered, then that's going to make for a much longer program than the equivalent Ada code: exception handling, without automatic range checking, doesn't have as much power.
Also, in Ada, all array references are checked for the boundaries. So you can't accidentally run off the end of an array in Ada. If you do, it raises an exception, giving you the chance to write code which can recover from the problem. Many of the bugs we see caused by `buffer overruns', such as affect sendmail, and the big one in Internet Explorer a few years back, and so on, are just not possible in Ada at all. Ask if anyone wants an explanation of how buffer overruns are exploited. Usually someone does.
There is, of course, a tradeoff situation here: while some of these tests can be mathematically eliminated, many cannot. All the extra testing does take time; not much time, but some. Since you can now buy a 1400-MHz AMD Thunderbird for $200, one could argue that we don't really have to worry about CPU time so much anymore. But some people, who really really really have to have lots of speed (such as those who write three-dimensional games), worry about this. Ada has a syntax to turn off array bounds checking, but it's arcane and the compilers are free to ignore it.
Three other features about Ada that I find particularly useful involve function and operator overloading, such as you are familiar with from C++, and optional function parameters with default values, also familiar from C++. But Ada also allows the use of `keyword parameters', sometimes called `named parameters'. For example, consider this function definition:
int Tax_Amount(int income; int exemptions = 1; bool blind = false;)
You can write code calling this function with just `Tax_Amount(10);',
and it will automatically fill in that you only have one exemption and
that you aren't blind. Or you could write `Tax_Amount(10, 2);', and
it will give you two exemptions.
But you cannot write `Tax_Amount(10, true);' because the compiler will think that `true' is supposed to be the number of exemptions, and will complain about type mismatch. (Some compilers may go on to compile the code anyway and give you something executable, just something that won't work.)
In Ada, you could write this:
function Tax_Amount (Income : Integer;
Exemptions : Integer := 1;
Blind : Boolean := False;) is [...]
And then call it like this:
foo := Tax_Amount(Income => 10, Blind => True);
And, if you name the parameters, you can put them in any order at all:
foo := Tax_Amount(Blind => True, Income => 10);
This feature, especially if one makes a point of always naming
the parameters when one calls a function, drastically reduces the
likelihood of accidentally switching around the order of a
function's parameters.
There is only one restriction: once you name a parameter, you have
to name all the rest of them too. That's because they may not be
in order anymore.
Consider another example. You've all used programs which take over the whole text window, such as an editor like vi or pico, or the bship game on elvis, and so on. These programs use a package called `curses', which controls the cursor. Historically, these programs were written in C, not C++, and so the package cannot use overloading for function names. As a result, there are functions like `move(y,x)', and `addch(c)' -- one moves the cursor, and the other puts a character on the screen. There's also a `mvaddch(y, x, c)', which moves and then adds a character in one line. And there's `mvwaddch(win, y, x, c)', which moves to a particular spot on a particular subwindow and adds a character.
In C++, you could fix this by writing several functions with the same name, but with different numbers of arguments. In Ada, the package goes one better: the name "Add" is overloaded, as expected, but instead of having a bunch of functions they only have two:
procedure Add (Win : in Window := Standard_Window;
Ch : in Character)
is
and
procedure Add (Win : in Window := Standard_Window;
Line : in Line_Position;
Column : in Column_Position;
Ch : in Character)
is
One that moves the cursor, and one that doesn't. As for whether you
specify a window, that doesn't matter, because it's listed as a
default. In C++, you can't do that: default-value parameters have
to be listed last. And the Ada code is more readable (and therefore more
maintainable) if you write it like this:
Add(Column => 0, Line => 2, Ch => 'b');
There's no question about what that does, right? Much better than this:
mvaddch(0, 2, 'b');
, not least because the latter example doesn't work: the 0 and 2 are
backwards. In C/C++ you can't list the names of the parameters
you are passing in, so there's no way to have the compiler do an extra
test for you. You'd have no choice but to manually inspect and check
each such function call to find the one that's wrong.
As with exception handling and strong typing, the power here isn't just because Ada has keyword parameters, or overloading, or default values. It's because it has all three features, which can be used together to help ensure correctness and improve readability.
Some years ago, a professor here named Mike Berman made up a list of common errors committed by students. I don't remember all of them, but three of them were using `=' for `==' in a condition, dangling else, and null loops or conditions. All three have in common that the compiler doesn't warn you about them, so they lead to runtime errors (such as an infinite loop or incorrect answers). Also, all three have in common that in Ada, you cannot compile them, so you'll never have to debug them. Null loops result from things like this:
while (c > 0);
c--;
Buried in the middle of a lot of code, that can be hard to spot, but
it's an infinite loop because of the extra semicolon. I've had students
do this in CS&P who spent most of an hour trying to find these.
In Ada, it can't happen, because you can't have an empty hunk of code. To
put in a `dummy loop', you would have to use the special statement `null'
and explicitly write it out:
while c > 0 loop
null;
end loop;
Also, as you might guess from that example, all blocks have to end with an
`end' of the right kind; end loop, end if, whatever. So there's never a
dangling else:
if (a > b) if a > b then
if (c > d) if c > d then
foo = 0; foo := 0;
else end if;
foo = 1; else
foo := 1;
end if;
The one on the right is longer, but there's no error causing the `else'
to be associated with the second `if' instead of the first.
And then there's the assignment in a condition:
if (a = 1)
b = -1;
This means that b will always equal -1. The reason is that in C (and
C++), an assignment returns a value (a = b = c = d = 0;), and an if
statement doesn't have to have a Boolean value, but rather considers
anything nonzero to mean `true'. Java attacks part of this problem;
assignment still returns a value, allowing the multiple assignment
above, but a conditional must evaluate to a Boolean type. So in
Java, the lines above wouldn't compile, but this would:
if (a = true)
b = -1;
And it would have the same problem. In Ada, assignment does not return a
value, and a condition must always be of type Boolean. So this:
if a := true then
b = -1;
Is just a flat-out syntax error. It does mean that you can't bulk
assign variables to the same initial value; assignment returning a
value does give convenience, as in an example like this:
while (X = numleft()) {
cout << X << " left to work on" << endl;
process_one();
}
This will keep getting whatever-it-is you get, and then stop when you're
done. It's short. But what if it was supposed to be an `==' in there?
Could you, could anyone, debug this without knowing what the rest of the
program was supposed to look like? This could be perfectly good code
that does exactly what it's supposed to. It could also be a bug. The
only way to find out is to read the rest of the program and determine
what it does and why.
The features that let you write this sort of code -- an empty loop body, an assignment inside a condition -- also make it possible to write code which is hard to debug. Assuming that code above is correct, you'd have to write the loop like one of these in Ada:
loop x := NumLeft;
X := NumLeft; while x > 0 loop
exit when X = 0; put(X);
put(X); put(" left to work on");
put(" left to work on"); new_line;
new_line; process_one;
process_one; x := NumLeft;
end loop; end loop;
It could be argued that this verbosity is a problem, and in some cases perhaps it is. Programs in Ada are usually longer than in C/C++. But suppose that C loop above is wrong. Then what does the Ada version look like?
loop while x = NumLeft loop
exit when X /= NumLeft; put(X);
put(X); put(" left to work on");
put(" left to work on"); new_line;
new_line; process_one;
process_one; end loop;
end loop;
They're totally different: comparing X to the result of the NumLeft
function doesn't look at all like assigning X to the result of the
NumLeft function. Since there's no ambiguity in assignment and
comparison, you can tell which one the code is supposed to.
Another way that Ada is pickier is in case statements:
switch (b) { case b is
case 'a': do_this(); break; when 'a' => do_this;
case 'b': do_that(); break; when 'b' => do_that;
} when others => null;
end case;
In C and C++, you don't need a `default' case, which says what to
do if nothing else matches. In Ada, all possible values of the
variable have to be accounted for or the compiler will refuse to
compile the code.
Also, note that Ada (like almost all non-C languages) does not fall through. In C/C++/Java, if you forget the first `break' statement, the result is that it will do_this() and also do_that(). This may be the result of an influence from a language called FORTRAN, which had an `arithmetic if', which is somewhat analogous to a switch/case. In FORTRAN, this fell through. However, some years ago Sun Microsystems did an analysis of C case statements looking for those with `break' and those without. They found that about 95% of all cases had `break' statements; in C, the default behaviour is wrong 19 times out of 20.
Of course, falling through lets you have more than one value for a case without repeating code; in Ada, there is a special syntax for that:
switch(b) { case b is
case 'A': when 'A' | 'a' => do_this;
case 'a': when 'B' | 'b' => do_that;
do_this(); when others => null;
break; end case;
case 'B':
case 'b':
do_that();
break;
}
As with many other things, Ada inherits this syntax from Pascal.
If you've been paying attention, you've probably noticed that Ada's syntax is usually longer than a language such as C++ (but not always, as with that last example). This is partly because there are more keywords and less punctuation. Many people find this a bit annoying when they write their first programs, because there's more typing and more lines of code. But when reading their first programs, the advantage of the verbosity becomes pretty clear.
One obvious way that Ada's syntax is cleaner can be seen when we turn to what C++ calls `templates', and Ada calls `generics'. You should be familiar with C++ templates; here's an example that I got from Dr. Baliga, alongside the Ada version:
#include <iostream> with Ada.Integer_Text_IO;
using namespace std; use Ada.Integer_Text_IO
template <class T> procedure Generic_Swap is
void exchange ( T & x, T & y ) { generic
T z = x; type Item is private;
x = y; procedure Exchange(X, Y: in out Item);
y = z; procedure Exchange(X, Y: in out Item) is
} Temp: Item;
begin
int main ( ) { Temp := X;
int a = 5, b = 10; X := Y;
Y := Temp;
exchange ( a, b ); end;
cout << a << " " << b << endl;
A, B : Integer;
return 0; procedure Swap is new Exchange(integer);
} begin
A := 1;
B := 2;
Swap(A,B);
put(A); put(" "); put(B); new_line;
end Generic_Swap;
All the double-colons and angle brackets in the C++ really make my eyes
hurt. And the ampersands mean that you are passing by reference: that is,
the actual parameters. (More on this later).
You'll see that the line `procedure Exchange' is repeated. That's because you can put the `generic' part in one file, and the actual body of the function in another file. The syntax here doesn't change just because we put them in the same file, so it looks a bit repetitive. Again, the language is designed for large systems in many files, not quick hacks.
I mentioned the ampersands mean `pass by reference' in C++; in Ada, the `in out' does that. As you might imagine, an `in' parameter is being passed to the procedure, and an `out' parameter is one being returned. An `in' parameter cannot be changed, and an `out' parameter has no value until it is set inside the procedure. This:
procedure Foo (x: in integer; y: out integer) is
z: integer;
begin
z := x + y;
end foo;
[...]
bar := 2;
foo(1, bar);
will give a compile-time warning: y is never assigned a value. You
passed in `bar', and you had given it a value, but because Y is an
`out' parameter, the value passed in was ignored. This:
procedure Foo (x: in integer; y: out integer) is
begin
X := 2;
end foo;
is not just a warning: this will give a fatal compile error. You
can't assign to an `in' parameter, period.
As an aside, when I asked Dr. Baliga for an example of a template function, he just wrote this one out on Elvis right then. But when he went to run it, it didn't work. The C++ syntax tripped him up, as he had an ampersand where he wasn't supposed to (I don't remember where). The program printed out `10 10' instead of `10 5'.
Another advantage to Ada is the astoundingly huge number of features it has. I've noted that some things you might want to do will take more lines of code; but other things which would be complex or impossible in C/C++ are built right into Ada. This means that a new person learning Ada will have to learn a lot of material to really be proficient, but I don't want my life support system designed by someone who was afraid to learn a lot about a programming language.
For example, suppose you are working with a floating-point problem, and you've read in a number and need to know what's the next highest floating-point value the computer can represent. You've read a value from a temperature sensor, or whatever, and the value in the variable is `98.13241234'. What's the next highest floating-point number on your computer? How would you find that in C/C++? In Ada, you could write:
Float'Succ(Coolant_Temp);
And it would return the successor to this input value, whatever that
was, as it is represented by the computer the program is running on
(on an Intel Pentium, it's `98.1324157714843648'). You might need to
know this, for example, to determine how precise the results of your
calculations will be. There is also a 'Pred attribute, which gives the
preceding number on the machine running the code.
In fact, all numerical types have attributes for 'Succ, 'Pred, 'Value, and 'Image. If you need to convert a string to an integer or float, or vice-versa, C lets you use sprintf() and atoi() or atof(). In Ada, Float'Image(24.11212) will return a string for the floating-point value, and Float'Value(Input_Line) will strip off the leading spaces and return the floating-point value of the string. There are also `Min' and `Max' Attributes, which take two arguments and return whichever one is larger.
And this happens automatically for all enumerated types, even those defined by the programmer. So I can say:
type Tone_Type is (Low_Do, Re, Me, Fa, So, La, Ti, High_Do);
Tone : Tone_Type;
[...]
Ada.Text_IO.Put (Tone_Type'Image(Tone)); -- Print as string
Tone := Tone_Type'Succ(Tone); -- up one note
There are also 'Min and 'Max attributes for user-defined enumerated
types; `Tone_Type'Min(Re,Fa)' will return `Re'.
Another feature having to do with numerical types is the precision specification. Suppose I'm sending a satellite to Jupiter. On a tirp to Jupiter, if you're off by 1% that's more than 10 million miles, so there's not much margin for error. My astronomy team reports that I need to handle numbers up to 9billion, and I have to have 10 digits on the right of the decimal point. So that's 20 digits total, with at a resolution of at least 0.0000000001. How do I know whether a float or a double in C/C++ meets those requirements?
In Ada, I can write:
type Measurement_Type is delta 0.0000000001 digits 20;
And the compiler will either (a) figure out some way to get me what I
need, or (b) refuse to compile the program. It may be that the CPU
I'm working with has an adequate floating-point type. It may be that
the compiler can figure out a way to use an integer type and just shift
the decimal by 10 spots when necessary. But either I'll get the
precision I need, or it won't compile.
Possibly owing to its intended use in embedded systems, Ada was originally hampered with a klunky and difficult string implementation. Embedded systems do not usually deal with strings as we're used to thinking of them; it's not like your electronic ignition module is going to print out an error message; even if it did, you're not going to wipe the goop off it under the hood every day to see if there's a message there. Recognising the DoD heritage, a cruise missile doesn't need to print out `bombs away!' on the side when it's launched.
This has been mentioned as a problem with adoption of Ada; while embedded systems don't make much use of string input and output, students learning to program do. Instructors looking for first languages to teach in Intro leaned toward other languages, because the Ada string implementation was so annoying. The reason it was so annoying is that it treats strings as arrays of characters, which means that they are fixed-length, and that Ada's strong typing gets in the way of simple string handling.
If you declare a string as being 20 characters, you have to put 20 characters in there. If you want to put in `Bob', you have to add 17 blanks. This assignment:
Name := "Bob";
Is taking an array of three characters and assigning it to an array of
20 characters, which produces an error: the types aren't the same.
Ada doesn't allow assignment between different sized arrays.
The modern string implementation is still much better, but it leaves something to be desired. For backward-compatibility and efficiency, it still includes the old-style strings, and string literals are taken to be old-style.
One more flexible string type is the bounded string. This lets you specify a maximum length, but if you don't use it all that's okay. Because of Ada's bounds checking, nobody can overrun the end of the string to execute unauthorised code. But you still have to convert literal strings to assign to the bounded strings, and you have to do lots of stuff to define a bounded string:
with Ada.Strings.Bounded;
use Ada.Strings.Bounded;
package Bounded_80 is
new Generic_Bounded_Length(80);
use Bounded_80;
B : Bounded_String;
B := To_Bounded_String("Bob");
Text_IO.Put_Line( To_String(B) );
The third kind of string is an unbounded string; it has no length limits.
Should somebody type in 500 characters, the package will allocate enough
space to hold them all. But again, you have to deal with the strong
typing:
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
U : Unbounded_String;
U := To_Unbounded_String("Bob");
Text_IO.Put_Line( To_String(U) );
You can dodge some this by using `rename':
function To_U_Str (Source: String) return Unbounded_String
renames Ada.Strings.Unbounded.To_Unbounded_String;
function Reg_Str (Source: Unbounded_String) return String
renames Ada.Strings.Unbounded.To_String;
[...]
U := To_U_Str("Bob");
And I'm not sure how to hit those square on. Ada's something of a niche language, and the design borrows from many other languages, so my list of features from Ada that other languages picked up is not really long. Polymorphism, function overloading, block structure, default parameter values, and so forth all have other parents.
I will note that in Perl, all blocks, such as in an if or while, must have curly braces around them; the C-style syntax that lets you do a one-line block without any markings is illegal in Perl. I don't know if that's inherited from Ada, but it does eliminate the dangling else and the empty loop caused by an incorrect semicolon. Similarly, C++ has default values for formal parameters, but I don't know if it was inherited from Ada, or from somewhere else.
As for features of Ada that other languages should adopt, many of the ones I've mentioned here are probably not useful for other languages. The attributes 'Pred, 'Succ, 'Max, 'Min, and so on would make many simpler languages a lot more complex, and it's not clear that would be much benefit. The strong typing is good for some jobs, but annoying for others.
I definitely think that keyword parameters, where you can give the formal parameter name when you call a function, are a complete win with no downside. It's optional, so it wouldn't break any existing code, and it would make it a lot easier to write code that avoids bugs.
The cleaner syntax for generics (as compared to C++ templates) may not be a language feature, exactly, but it's a drastic and huge improvement. Also, the use of `in' or `out' keywords to specify which parameters you are going to change and which you aren't is vastly superior to having ampersands do that job. `in' is only one character longer than an ampersand, but its easier to read and not ambiguous. And you can type it without hitting the shift key, so it's the same number of keystrokes.
Array bounds checking is mostly a win; it should probably be the default in every language, with the ability to optionally turn it off for some sections of code. Many well-known security exploits have taken advantage of buffer overruns, and that's just not a decent tradeoff. C was invented before there was an Internet; it's no longer a friendly world for computers compared to the 1960s, and more things should be secure by default. Java does bounds checking; I don't know if that's inherited from Ada, or from some other language, but it's definitely worthwhile.
The only real problem with Ada isn't a feature, but rather the lack of one: a special string type. The original string handling mechanism in Ada was horrible, and the new improved versions aren't really all that good. New languages which are developed should definitely learn from the Ada string mechanism and make a point of doing things differently.
But when I'm laying in the operating room, and they're putting me on a respirator for surgery, I won't worry too much about whether that respirator has great text-handling features. I'll want to know that it's going to work. And if the respirator was programming in Visual Basic, I might just decide to find myself another hospital.