From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,19e983c5955f75f X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 2001-04-05 14:06:40 PST Path: supernews.google.com!sn-xit-02!supernews.com!nntp-relay.ihug.net!ihug.co.nz!news-hog.berkeley.edu!ucberkeley!enews.sgi.com!newshub2.rdc1.sfba.home.com!news.home.com!news1.sttls1.wa.home.com.POSTED!not-for-mail From: "Mark Lundquist" Newsgroups: comp.lang.ada References: <9af9ao$6ee$1@taliesin.netcom.net.uk> Subject: Re: Learning Ada (newbie) X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 5.00.2314.1300 X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300 Message-ID: Date: Thu, 05 Apr 2001 21:06:39 GMT NNTP-Posting-Host: 24.20.66.55 X-Complaints-To: abuse@home.net X-Trace: news1.sttls1.wa.home.com 986504799 24.20.66.55 (Thu, 05 Apr 2001 14:06:39 PDT) NNTP-Posting-Date: Thu, 05 Apr 2001 14:06:39 PDT Organization: Excite@Home - The Leader in Broadband http://home.com/faster Xref: supernews.google.com comp.lang.ada:6531 Date: 2001-04-05T21:06:39+00:00 List-Id: Ayende Rahien wrote in message news:9af9ao$6ee$1@taliesin.netcom.net.uk... > What are the advantages & disadvantages of Ada compare to C or C++ > and other languages? OK! I'd like to take a whack at that question... "Other languages" of course is pretty broad... :-) so most of my comparisons will be with the "C-class" languages, C/C++/Java (and there are others on this group who know Java a lot better than I do, so they can add to what I have to say). Someone like Brian Rogoff :-) can handle comparing Ada with functional languages like OCAML. You probably already understand the issues involved w/ scripting languages vs. compiled languages, so I'll leave that alone. I'll give a summary first, then go into more detail in a bit. The bottom line is: "Done sooner, fewer bugs, less pain". And over the lifecycle of a long-lived project, I think that well-written Ada code is more maintainable than well-written code in many other languages (of course it is possible to write crap in any language, and there isn't much point in comparing the crap :-). This benefit accrues primarily to two factors. The first factor is the expressive power of Ada, which translates directly into better understandability of code written in Ada. I think a programmer who really knows both Ada C++ (for instance) can communicate "intent" better in Ada. The second factor has to do with early detection of programming errors. Let me try to explain this.. :-) When you make a programming mistake, the result is going to fall into one of four categories: 1) The compiler will reject your code. Or, 2) Your code will compile OK, but when you try to link your program you will get errors (undefined symbol references). Or, 3) Everything compiles and links OK, but when you run your program it blows up with an unhandled exception. Or, 4) No exception is raised, your program just goes beserk. This result can range from subtly or occasionally incorrect behavior or results, to fatal errors (e.g."segmentation fault"), to system hangs, to destructive crashes. Compile-time errors are generally the easiest thing to figure out. If I make a mistake that results in one of these errors, it could be that I just made a simple mistake that violated one of the language definition rules. In that case, I just figure out what rule I broke and fix the code. Other times, the violation of a language rule points to some underlying logic error or design error. Now I have to step back and do some redesign, but then I'm glad that at least my error was one that could be caught at compile time instead of later, because later means more head-scratching and farting around to figure out what the problem was. A robust, programmer-friendly language would be consciously designed to "shift" the manifestation of errors along the scale toward the "compile-time" category and away from "unbounded run-time" category. This is just what Ada is designed to do. The designers of Ada tried to eliminate as much nonsense as possible at compile time. For instance, an Ada "function" is analogous to a non-void-returning function in C (the analog of a void-returning function is an Ada "procedure"). Now, if you write an Ada function with no "return" statement, the compiler will reject it because this is not legal Ada. But in C, it's perfectly legal for a non-void-returning function not to have a return statement. The result of this at run-time is that the caller simply takes as the return value whatever happens to be in the return-value register. Don't miss the fact that this behavior is in fact the *meaning* of that formulation in C. Now how likely is it that the programmer intended this meaning? Fat chance... How likely is it that he/she just forgot to write the return statement, or deleted it inadvertently? Pretty likely. Now consider that the resulting error may not appear until well after the product has been released to the user community. That's just one example out of many. Another is Ada's "case" statement, compared to the fall-through semantics of C's "switch" statement. And it's well-known that in C, a simple typo of "=" in place of "==" (or vice-versa) can escalate right up to an unbounded run-time error. From the syntax level all the way up, Ada was consistently designed to catch these kinds of errors at compile time, and it does this without imposing burdens on the programmer. Link-time errors are more of a pain than compile-time errors. The compiler has all kinds of information that the linker can't see, so a compiler is able to give error messages with a lot more specifics about what went wrong. All a linker can say is "I couldn't resolve symbol X", and then it's up to me to figure out what I did wrong. In Ada, linker errors are virtually unknown. The only times you ever get a linker error are due to (a) linking against modules written in other languages; (b) linking against object module archives (which is legitimate, but outside the scope of what is defined by the Ada language, or (c) a bug in the Ada language implementation (compilation system or whatever). You never get a link error when linking an ordinary, self-contained Ada program. Once you get into run-time errors, it's a whole different ball game. A lot of times, finding the problem means debugging, which is more or less pain depending on the nature of the program and the nature of the error. For a simple, small program, it's not bad. For a large system that's heavily state-dependent and timing-sensitive, debugging can be next to impossible, i.e., doing it is going to require a serious investment in time and creativity. The "exception" error mode is preferable because (a) it gives you a good hint of where to start when debugging or otherwise investigating the problem, and (b) it represents a boundedness on the error behavior of the program; that is, the error is being "caught" at some point by the program itself rather than going on to wreak more havoc. The program may have no better way to deal with the exception than to terminate, but in that case this is still better than not having raised an exception at all. When an error isn't caught by a run-time check, often the result is a chain-reaction of cascading error effects in the program, and it's not uncommon to begin investigating by debugging a second- or third-order downstream effect of the error (for example, the error causes corrupted data which is later read and causes the observable incorrect behavior). Obviously, in C all run-time errors fall into the last category (unbounded run-time error), since C doesn't have exceptions. C++ has few run-time checks (bad_cast, bad_typeid) that throw exceptions, so unhandled exceptions usually originate with an explicit "throw" in the program rather than a language-defined check. Java defines a few more run-time checks (such as the array vounds check), but not as many as Ada. Ada defines a large number of run-time checks that raise exceptions, which would otherwise result in unbounded errors. Better yet, Ada's language rules are constructed in such a way that the compilers can often optimize away a suprising number of the language-defined checks. Java doesn't have this ability to the same extent. (It's often asked, "Don't the run-time checks carry a lot of run-time overhead?" The answer is, first of all: "Not as much as you might think", but more importantly, Ada gives you the choice. All the run-time checks can be suppressed, either through pragmas in the source code or compilation options. So you get to decide the cost-benefit tradeoff as you see fit). Blow-by-blow, here are the technical aspects I see contributing to the factors of "expressive power" and/or "early error detection". 1) I think one of the coolest things about Ada is its 'package' construct. Packages represent the programming concept of a "module" and are absolutely fundamental in Ada. The package construct unifies, very cleanly and elegantly, three important concepts: (1) encapsulation (which is about privacy, i.e. hiding an abstraction's representation from its clients), (2) separation of interface and implementation, and (3) namespace control. Every package has a construct called a "specification" (the interface), and most packages, depending on the contents of the specification, also require a "body" (the implementation). The idea of separation of interface and implementation calls for more than just textual separation, it implies a "contract" specified by the interface which the implementation is obligated to fulfill. So in Ada, if the body is incomplete or incorrect with respect to the spec, you get an easy-to-understand compilation error when you try to compile the body. If you do not provide a body for a package that requires one, then you'll get a prelinker error when you try to link the program (not a linker error complaining that a screenful of symbols is undefined, but a clear error message that you are "Missing body for package Foobar" or whatever). Compare this with C/C++. The interface is typically given by a ".h" header file containing extern declarations, and the implementation is given by a .[cC] file. But there is no language-defined correctness/completeness relationship between the header file and the implementation file, and the identification of either one with a "module" is entirely notional. If the "implementation" doesn't match, the code is still perfectly legal and will compile just fine. The backstop for catching this is the linker, when it can't resolve all the symbols. Moreover, you can only do this if you are in a position to link a main program, which is an annoyance when developing libraries or developing components of large software projects. And it's quite easy to violate the interface/implementation contract in ways that are not caught by the linker, so and so will cause a run-time error. C/C++ has three separate mechanisms to handle the three aspects of modularity: classes for encapsulation, namespaces for namespace control, and the .h/.c convention to simulate separation of interface and implementation; but the three mechanisms don't fit together snugly. Some other random notes... If you want to inline a member function, it must go in the class declaration, i.e. the header file (thus violating separation). The "namespace" construct in C++ also is inferior to the namespace control provided by Ada packages (don't have time to go into detail on this). In C/C++, namespace control must largely be implemented through ad-hoc policies that must be manually checked and enforced by a human "name czar" (see the book "Large-Scale C++ Software Design", by John S. Lakos -- it covers high-maintennance techniques for working around this and other problems that don't exist -- at least to nowhere the same degree -- in Ada, such as circular compile- and link-time dependencies). Ironically, one gripe against Ada is that it has too many rules. With other languages, instead of language-definition rules that work with you to help you express intent, you have to submit to labor-intensive project policies if you want the project to succeed. Compare with Java and Eiffel... In both of these languages, a class's interface and implementation are not separated. Java has a mechanism (the "interface" construct) that can be used to simulate this, but that's not really what it's meant for. I think Eiffel also has a construct that can be used to achieve some separation of interface and implementation. But in both cases it would be somewhat onerous to implement a "modularized" design using these features, and the result would be code written in an unnatural style for those languages. Embracing packages is the "library unit" concept in Ada which allows for true separate compilation of modules while maintaining semantic relationships between them. One result is that the reliance on makefiles for codifying compilation dependencies is rendered obsolete; the compilation system can do all the necessary dependency analysis on the fly. The bottom line is that since modularity is fundamental to programming, it should be primitive in a programming language. 2) Ada has a powerful type system. Some have called this "strong typing", and strong static type checking is indeed part of it, but not all. It's not just that the type system is "strong", it's that it's also "rich". One aspect of this is the ability to create user-defined types -- not just record types (which are like C/C++ structs and classes), but user-defined numeric types which are distinct from each other and the predefined numeric types, user-defined array types (most array types in Ada are named, while in C-class languages they are all anonymous), real enumeration types that are not aliases for integers and can be used as array index types (and that don't collapse into ints as soon as you get in with a debugger), and more. Another aspect of "rich" typing is the very cool concept of "type/subtype" (no time to go into this right now, and covered in at least two other recent threads on comp.lang.ada). The classic example of why you need distinct types even for numbers is something like this: function Area (Radius : Meters) return Square_Meters; where the types "Meters" and "Square_Meters" both happen to have the same representation (say, a floating-point number), but are clearly not the same type. If you take something of type Square_Meters and try to pass it as the parameter to Area, you want the language to tell you at compile time. Packages and the type system head up my short list of technical advantages. A few others: 3) Generic units, which are similar to C++ templates except that they are almost perfectly type-safe and compile-time checkable. They also implement a programming concept called "constrained genericity", which you can read about on the Web or wherever (no time to go into it here). For some, in whose minds Ada went overboard in requiring explicit instantiation of generics, Ada generics do not represent the ideal but are still preferable to C++ templates. When you make a coding mistake when working with Ada generics, you get brief and informative compile-time error, where the comparable mistake in C++ can result in a linker error message that is truly epic in size and whose cryptic syntax renders it virtually unreadable (if you've ever used STL, then you know what I'm talking about! :-) 4) Safe pointers. 6) A crisp model for inheritance and dynamic polymorphism that is *not* based on the idea of a "class". IMHO, class-oriented languages (Simula/Smalltalk/C++/Java etc.) embody an intellectual error in their treatment of encapsulation (privacy and primitive operations, a.k.a. methods), by making the the "class", which is really a type definition, also the unit of modularity. The conflation of "module" and "type" in the notion of "class" results in all kinds of distortions: special syntax and sematic complexities for various types of constructors (constructors as a language-level concept do not exist in Ada, since they are unnecessary without classes), the need for a "singleton" idiom, and the need for "friend" classes. Also, Ada's dispatching model is nice and clean. Inheritance does not imply dispatching, and dispatching is a property of the method invocation, not just the method declaration. And you can dispatch on the return type of a function, not just parameter types. 7) Limited types. In C++ if you want to define an abstraction that retains complete control over its own instances, e.g. does not allow clients to copy instances or test for equality, you have to jump through some hoops -- declaring the abstraction as a class, then declaring private equality/inequality operators private and constructors. In Ada, the idea of a "limited type" is primitive, and you get it by including the single word "limited" in the type declaration. 8) True array types, including constrained and unconstrained array types and multidimensional arrays. The concept of an "array" is another fundamental programming concept, and collapsing to the pointer-based, machine-model level a la C/C++ doesn't do it justice. 9) Support for tasking and task communication/synchronization, at a higher and nicer level of abstraction than the "thread" level. The tasking model allows all kinds of errors to be caught at compile time that are simply impossible when coding to thread-level library routines. The task priority model is unified with interrupt priorities. 10) To make it an even 'Top-Ten List' :-)... The Ada Reference Manual is a masterpiece of definition. That's about all I can say about it! It could go on, the list of advantages by no means ends there. OK, fine-grained control over the machine-level representation of data structures... package elaboration... lexical scoping... better string handling... Compared to other mainstream languages, Ada holds a lot of cards in terms of technical advantages. Some technical disadvantages: 1) A lot of people think Ada would be better if it had more support for something like Eiffel-style "Design By Contract" (preconditions, postconditions, invariants). Personally, I'm undecided, but quite intrigued. 2) That's about all I can think of right now. That doesn't mean there aren't a lot of things I'd like to see improved, I just don't think the things on my wish list rise to the level of "disadvantages", especially compared to C/C++/Java. > 3. I read in Jargon File that "hackers find Ada's exception handling & > inter-process communication particularly hilarious." among other stuff. Why > is that? In all honesty, nobody knows. This is just somebody talking out of the wrong orifice :-) The Jargon File seems to be a pretty good source for information about jargon, i.e. slang terms. I doubt if it's much good for anything else. In addition to the "particularly hilarious" nonsense, the JF entry perpetuates the myth that Ada was "designed by committee" (patently un true, as a matter of public record) and refers to Ada's "elephantine bulk", which is hardly fair... Once the C++ standard was published at long last, it was basically just as huge, and even at that, success in C++ still depends on a large knowledge structure that falls outside the language itself -- linker and makefile details, the standard libraries, threads libraries... but mostly a vast body of knowledge of "pitfalls" about which whole books have been written. These pitfalls are all the same kinds of things that were designed away in Ada, whose language definition is roughly the same size. Mark Lundquist Rational Software