Modula-3 and Ada A summary of the differences between the two languages, (and a touch of C++). I spent a lot of time programming in Ada, mostly on things that also process Ada code, including working on two different compilers and a general front end. At one time I had most of the semantics of the 83 dialect in my head. Ada has a similar list of useful "features" as Modula-3, but is far more complicated. Economy of concept is quite low. There are 511 pages in the Ada-95 definition, counting the same way that Modula-3 counts its 50 pages (no introduction, no syntax summary, no libraries, etc.) I used the annotated version of the Ada manual that includes all the clarifications and interpretations that users and compiler-writers demanded. Similar clarifications and interpretations have been integrated into the Modula-3 definition. Things Modula-3 has that Ada doesn't: Sets. If you've used them, you will greatly miss them. Pascal and Modula-2 also have them. They are a strongly-typed and high-level replacement for the bitwise oring, anding, etc. of bitmasks, poorly disguised as integers, used in C and C++. Partial revelation. This is far more flexible as an information-hiding mechanism than the private types of Ada. It allows the fields and methods of a type to be partitioned into an unlimited number of subsets and any scope can see any subset of the subsets. The private types of Ada give all-or-nothing visibility. Garbage collection. On one medium-sized project in Ada that I was involved in, the decision was made to first implement without bothering to deallocate, get that working, then add deallocation in a later development phase. That second phase took the same amount of time as the first, by the time we got the observable dangling pointer bugs tracked down. It could have been somewhat less trouble if we had designed-in the deallocation from the first, but it still would have been a mess. That experience really converted me to a believer in garbage collection, after years of skepticism. On the other hand, if you really must use explicit deallocation, Modula-3 has an alternate heap that works that way, so you can choose. Things Ada has that Modula-3 doesn't: Variant records. I recall that at one point, Modula-3's designers found leaving them out was a major savings in complexity. A hierarchy of object types can be used instead for the same purpose. Ada-83 isn't object oriented, so they were needed there. Ada-95 is, but it still has the variant records. Modula-3 has a TYPECASE statement, that is needed to conveniently use an object hierarchy instead of a variant record. Local variables and function results of types whose size is not static. I have always hated heap-allocating anything that had LIFO lifetime, so I use these in Ada regularly. They introduce some semantic complexity and require some care in use, often extra nested blocks, etc. There is hidden, unexpected (for many programmers) space and time overhead in their implementation. Having a garbage collector in Modula-3 makes them of little use. User-defined overloading. My experience with Ada has made me a vehement foe of user-defined overloading. It's a semantic train wreck. The great majority of Ada's tenfold extra complexity disadvantage compared to Modula-3 derives from this one (mis)feature and its myriad interactions with practically everything else in the language. In one way, it's not as bad as in C++, in that there are very few implied conversions that can be used to make a candidate function fit a call, and there's nothing like the relative goodness of fit that C++ uses. But, unlike C++, the candidate functions can come from many different scopes. Also unlike C++, many of the builtin operators can be replaced by programmer-defined functions that are called using the same operator notation. This in turn means that builtin operators are not necessarily visible, unlike every other language I know of. Worse, the result type of a function/operator participates in the overload resolution, along with the parameter types. This means overload resolution information can flow in all directions in an expression tree (actually, an even bigger region, called a "complete context"), in from the top, out the top, up from and down to any of the descendants, and crosswise, at every call/operator location. As an example, the seemingly innocent notation "A(I)" has 17 possible structurally distinct interpretations. One example interpretation is this: A is a function that is implicitly called, with no parameters, returns a pointer, the pointer is implicitly dereferenced giving an array, the array is subscripted (the parentheses--the only syntactically explicit operation in the example), the subscript being the result of implicitly calling function I. On the other hand, only a subset of the "builtin" operations can be overloaded. For those that can't, virtually every one has a different rule about the subset of the directions overload resolution information can flow. The ellipsis "..", has, as I recall, 5 different rules, in 5 different contexts, which, mercifully, are at least syntactically distinguished. Without overloading, procedure/function identity is determined just by whether the names are the same. With overloading, it has to take into account, some portion of the entire signature. In Ada 83, as I recall, there are 5 different rules, used in different situations, as to just what subset this is. All of this mess only buys saving the effort of thinking up different names for your procedures, a few keystrokes typing them, and, in some cases, the readability of operator notation over function call notation. It is syntactic heroin. The first couple of fixes may feel really good, but you will pay a heavy price indeed, in the end. Fixed point data types, i.e. non-integer fixed point. If you really want these, Ada is the only game I know of. They are rarely used. I worked peripherally with one embedded systems project that used them. They interact with the overloading and operator visibility rules in an ugly way, creating a glaring inconsistency in the rules. Enclosing scope qualifiers. This is the only thing in Ada that I wish Modula-3 had. The qualified reference A.B works where A is the name of some enclosing scope and B is declared local to it. When using nested procedures, I think this really helps readability when referring to a identifier declared local to a procedure that is not innermost. Also, when B is declared in interface A, it allows moving code in or out of A or a module that exports A, without having to edit the code. Things that are different: Ada requires one-to-one match between specifications (interfaces) and bodies (modules). Most Modula-3 code is written this way, but it doesn't have to be, and there at least one group of library routines that are not, that have a well-reasoned and carefully documented case for their design. In contrast, Ada can make it very difficult to reorganize things. But Ada's rule does make it a lot easier for a compiler to find the source files that are are needed. Private (opaque) types. Ada puts the equivalent of a revelation in the package spec (interface), so the compiler can always know the revealed type. Language rules prevent client source code from utilizing this information. The problem is, in a multi-person project using any kind of source code control, data structure changes that should be only the body (module) writer's business require that the spec (interface) be checked out, changed, etc. Then all client code implementors must at some time, upgrade to the new interface, review what has changed to determine that it requires no source code changes for them, then recompile, possibly massive amounts of code. On the other hand, it does allow you to have an private type that is not heap-allocated. Generics. Ada's generic facility is extremely complicated, but not as flexible. Generic parameters are restricted to a complex but incomplete subset of the kinds of declarable entities. On the other hand, it does allow most semantic checking on a generic unit to be done independently, without any instantiation. The only semantic checks done at the time of instantiation are actual/formal matchings of the generic parameters, similar to calls on procedures in an interface. There is one exception to this in Ada 83, that was fixed in Ada 95. The simplicity plus flexibility of Modula-3's generic facility has a cost. Very little semantic checking can be done on an uninstantiated generic unit. The checking is all deferred until instantiation time. Threads. Ada's thread facility has complex semantics and a lot of runtime overhead, even at many block entry and exit points where nothing happens that the programmer might expect to have anything to do with threads. The synchronization method, the "rendezvous", has high overhead and is very confusing in that it attempts to create the illusion that you can "call" another thread like a procedure. I have had so much difficulty trying to get students to understand how threads are different from procedures, that I hate this idea. Many embedded system projects couldn't tolerate the overhead and just resorted to using thread libraries instead. Ada-95 added "protected" types that are implicitly subject to mutual exclusion and can be much faster, but of course, mutual exclusion is not a complete synchronization system. Parameter modes. Formal parameter modes in Ada are "in", "out", and "in out", denoting the intended use. For some combinations of mode and type, the actual mechanism (reference, copy-in-copy-out) is compiler-writer's option. This choice can alter the semantics of a program, but the language makes no attempt to determine whether this can happen. It's undoubtedly undecidable. If you write code that depends on the mechanism, Ada defines your program as "erroneous", which means the meaning is undefined, but you won't get any error message. Of course, C++ has this kind of undefinedness all over the place, Ada only in a few places. Object orientation. Ada-95 added something like object orientation, in a strange sort of way. If T is the name of a "tagged" type (i.e., one that can "extended" (subtyped)), T contains only those values that are not members of any extension of T. For the latter, there is the type T'class, which includes all possible extended values as well. All other object-oriented languages put the fields and methods (and nothing else) inside a syntactically delimited construct, the object type, in the case of Modula-3. Ada syntactically packages the fields, but the methods are scattered around anywhere in the same scope as the type. Any time there is a procedure with a formal parameter of a tagged type, it is a method of that type. It need not be the first parameter.