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: f5d71,d275ffeffdf83655 X-Google-Attributes: gidf5d71,public X-Google-Thread: f849b,d275ffeffdf83655 X-Google-Attributes: gidf849b,public X-Google-Thread: 109fba,d275ffeffdf83655 X-Google-Attributes: gid109fba,public X-Google-Thread: 101b33,d275ffeffdf83655 X-Google-Attributes: gid101b33,public X-Google-Thread: 146b77,d275ffeffdf83655 X-Google-Attributes: gid146b77,public X-Google-Thread: 1108a1,d275ffeffdf83655 X-Google-Attributes: gid1108a1,public X-Google-Thread: 103376,d275ffeffdf83655 X-Google-Attributes: gid103376,public X-Google-Thread: 115aec,d275ffeffdf83655 X-Google-Attributes: gid115aec,public From: Matthew Heaney Subject: Re: Ada vs C++ vs Java Date: 1999/01/22 Message-ID: X-Deja-AN: 435576673 Sender: matt@mheaney.ni.net References: <369C1F31.AE5AF7EF@concentric.net> <369DDDC3.FDE09999@sea.ericsson.se> <369e309a.32671759@news.demon.co.uk> <77ledn$eu7$1@remarQ.com> <77pnqc$cgi$1@newnews.global.net.uk> <8p64spq5lo5.fsf@Eng.Sun.COM> <8p6vhi5mv34.fsf@Eng.Sun.COM> <8p6yan1xger.fsf@Eng.Sun.COM> NNTP-Posting-Date: Fri, 22 Jan 1999 02:10:26 PDT Newsgroups: comp.lang.ada,comp.lang.c++,comp.vxworks,comp.lang.java,comp.java.advocacy,comp.realtime,comp.arch.embedded,comp.object,comp.lang.java.programmer Date: 1999-01-22T00:00:00+00:00 List-Id: Mike Coffin writes: > Matthew Heaney writes: > > > I am interested in specifics. List 5 specific language features by > > which you conclude Ada is "complex." > > I'll have to answer based on Classic Ada; I haven't had occasion to > look seriously at the latest version. > > Here are some broad areas that I think are way too complicated: > > 1. Generics. Ada generics are much more complicated and harder to use > than need be. I'm not sure which aspects of generics you find "way to complicated." Is it the declaration, or the instantiation? Give some specific examples of generic syntax that you think are too complex. Or give an example of an abstraction that you wanted to turn into a generic, but were unable to. The other point is --something Robert Dewar brought up-- is what exactly do we mean by "complex." Do we mean language definition, compiler implementation, work for the programmer, or complexity of the resulting application written in that language? Your original argument (made in jest, I know) was that because Ada was too complex, that one should choose C as the implementation language, because it is a significantly simpler language. However, C offers no support (beyond preprocessor macros) for writing abstractions that are independent of the specific type. For example, suppose I declare a struct, and then need to put those struct objects on a stack or in a queue. Yes, I can write code to implement those abstractions for that specific type. But then what do I do for all the other structs that need to go in a queue? I probably have to re-write those abstractions, thus duplicating code, or, write something that isn't type safe, thus increasing the frequency of defects. So even if we posit that Ada generics are "way too complicated," they are better than the alternative offered by C (nothing). Abstractions written using generics means there'll be lots less code to maintain, and that the code that is there will have far fewer errors. > On the other hand, if you're of the opinion that exceptions do > happen, and that type exceptions are just another kind of > exception, OO type systems such as Java and Smalltalk are a *lot* > simpler than Ada's. I'm not sure what you're trying to say here. What is a "type exception"? No, Ada is not a "pure" language in the Smalltalk sense. But this is a proud claim, not an embarrassed confession. The language gives you more tools to do the job, and doesn't force you to wear a straight jacket as you do in a "pure" language. The richness of the type system is thus one of Ada's strengths. (Aside on programming language purity vs consistency.) Someone started a rumor that "pure" is defined as "better," but I don't buy into this argument at all. Consistency is much more important. I learned this when I started programming in Ada95. As I was wending my way around the new features of the language, I would sometimes guess when I wasn't sure about syntax, and sure enough, the code would compile! The Ada95 designers understood the Ada83 way of thinking, and preserved that. The language really is upwardly compatible, right down to the mind-set you have when you "think in the language." A tip of the hat to Tucker and the rest of the design team for a job well done! (End of aside.) > 2. Rendezvous. Compare accept/select/when, with all their > permutations and ramifications, with SR's "in" statement which is > much more powerful. (This is chiefly because "when" clauses can't > reference entry parameters in Ada). In fact, compare all of Ada's > interprocess communication with SR's. SR's is much more powerful > and easier to use. (But I'm biased; I worked on SR back when the > world was new :-) I'm unfamiliar with SR, so I can't make a comparison. It's a language? Is there a standard reference book, or perhaps a web-site describing the language? (The Ada tasking model is based in Tony Hoare's CSP.) What programming problem do you have that was made difficult or impossible because a guard (a "when clause") couldn't depend on the value of an entry parameter? Maybe you could solve the problem in Ada95 by using a requeue statement, which allows a called task to move a caller to another entry queue (say, based on a value of an entry param). You can also do a lot of slick stuff using a protected type. Ada and Java are the only mainstream languages that have built-in support for currency. Even if Ada's tasking features are too complex or not powerful enough (a criticism that only applies to Ada83), it's still better than the alternative offered by C and C++, which is nothing. In those languages, you have to leave the language and make OS calls, which are easily more complex, and less portable. > 3. Treatment of variables shared between tasks. Task A changes a > variable. When does task B see the change? Trying to maintain too > much generality made Ada much more complicated than any language I > can think of in this regard. It's not so much that the language > rules are complex, it's that writing programs that work reasonably > given then weak language rules is so hard. Anyone know of another > language that tries to conflate shared-memory and distributed > programming? The model for intertask communication in Ada83 is provided by the rendezvous. If you try to signal a task via some other mechanism such as shared variables, then you are being naughty, and might not get the behavior you intend. It shouldn't surprise you that support for shared variable communication is weak in Ada83, because there is none, except for some vague stuff about pragma Shared that only applied to scalar types anyway. In Ada83, if you want two tasks to communicate (ie for one task to "see" that a variable has changed), then you have to use a rendezvous. Support for shared variables has been vastly improved in Ada95. Support for distributed programming is provided by the Distributed System Annex in Ada95. That annex is very very nice. > 4. Parameter passing. Three modes; named parameters; default values > (but not for in-out parameters, for some reason). In spite of all > the features, the behavior is mostly unspecified for arrays and > records. Compare with rules of C, Java, or almost any other > language: they lack *all* these nifty features and seem to get > along fine. C++ of course being the exception. You seem to be arguing about two different themes: parameter passing modes (in, out, in out) and parameter passing mechanism (by value, by reference). Let's handle the parameter passing mechanism first. The Ada83 RM mandated that scalar types be passed by value (copy in, copy out), but left passing mechanism for composite types (arrays, records) up to the implementation. Ada95 added two more rules. First, tagged types are passed by reference. This solves the ugly "slicing" problem you can have in C++. Second, limited types are also passed by reference. This solves the problem of abstraction violations that can occur when limited objects that are passed by value are aliased. Now, let's talk about parameter passing modes. There is nothing magic going on here: in means the client must provide a valid value for that parameter, and has a guarantee that the object won't get changed out means the server has an obligation to provide a value for that parameter in out means the client must provide a valid value for the parameter, and that the value of the object may get modified by the server Parameter passing modes thus explicitly document what everyone's responsibilities are when a subprogram call happens. I regard this feature of the language as a Good Thing. The problem with C is that it's too low level, and the programmer has to do a lot of mental gymnastics to determine what a subprogram call means. He must mentally translate low level calls that require pointers into the higher level semantics ("Oh, let's see, a param that's pointer-to-int means that it gets a value. Yeah, I think that's it."). This semantic information is directly expressible in Ada. The other problem is that in C, the meaning of a parameter is frequently ambiguous. Does the subprogram void foo (char *s); mean that an array of characters in being passed in, or does it mean that a char is being passed out? At least in Ada, I can say procedure Foo (S : in String); procedure Bar (C : out Character); and there is no ambiguity. The less things for me to think about, the fewer things I'll get wrong. > 5. Overloading. Do result types matter? Yes. This is one of the big improvements over Pascal. > Do parameter names matter? Not in a declaration. Only types matter: procedure Foo (Bar : in Integer); procedure Foo (Bar : in Float); are a legal pair of declarations. However, the declarations procedure Foobar (Foo : in Integer); procedure Foobar (Bar : in Integer); would be illegal, because parameter names aren't considered part of the subprogram's signature. Parameter names can be used to disambiguate an invocation, though. For example, given these declarations: type American_Color is (Red, White, Blue); type French_Color is (Blue, White, Red); procedure Fly_Flag (American : in American_Color); procedure Fly_Flag (French : in French_Color); then the invocation Fly_Flag (Red); is ambiguous, because the compiler doesn't know whether you want to fly the American flag or French flag. To tell the compiler which one you mean, you can use named notation in the call: Fly_Flag (French => Red); which gives the compiler enough additional information to allow it to determine which subprogram you want (here, to fly the French drapeau). However, this feature of the language is seldom used, because most programmers don't know about it, and you don't always have a differently named parameter anyway (say the parameter had been named Flag for both operations). A better way to resolve the ambiguity, IMHO, is to use type qualification: Fly_Flag (American_Color'(Red)); or Fly_Flag (French_Color'(Red)); This is a more direct way of explicitly indicating which subprogram you intend to call. Note that when there's an ambiguity, the program is illegal, and the compiler refuses to compile the program. It's never the case that the compiler just chooses one or the other for you. When there's an ambiguity, you the programmer must state explicitly which subprogram is intended. > Do default values matter? Why, or why not? No. All default values do is allow you to omit parameters at the point of call. For example, Integer_IO.Put looks something like: procedure Put (Item : in Num; Width : in Field := Default_Width); Most of the time, the default width is what you want, and so you can just call Put with a single param: Put (X); However, it's still possible for there to be ambiguity at the point of call. For example, if I had another procedure with the profile procedure Put (Item : My_Integer_Type); directly visible in the same scope as Integer_IO.Put, then the call Put (5); would be ambiguous, because the compiler doesn't know which one you mean: Integer_IO.Put with a default value for Width, or the Put that takes My_Integer_Type? Of course, when there's an ambiguity like this, then the program is illegal, and won't compile. A default value is never used to resolve an ambiguity. For example, there's no preference rule that says, "call the subprogram that doesn't have any default parameters." As with the earlier example, the programmer must state explicitly which subprogram is intended. Note also that parameter passing mode isn't used to resolve ambiguities either. > And, hey: parameterless subroutines and enumeration literals look > sort of the same. Yes they do. The reason is so that you can have more than one identically-named literal in the same scope. That way, I don't have to do kludgey things like type American_Color is (Amer_Red, Amer_White, Amer_Blue); type French_Color is (Fr_Blue, Fr_White, Fr_Red); because the compiler is too stupid to know which type is intended, if you use just the name "Red". Given these declaration: type American_Color is (Red, White, Blue); type French_Color is (Blue, White, Red); procedure Foo (American : in American_Color); procedure Bar (French : in French_Color); then the calls Foo (Red); Bar (Red); are perfectly legal, because Foo takes American Red, and Bar takes French Red, and the compiler can figure out which is which. There is no ambiguity here. > Can they overload each other? Can they hide each other? If so, > when and where? You know, I've never tried to declare a parameterless function and an enumeration type with an identically named literal in the same scope, so I can't tell you. About the only time you use parameterless functions in Ada is to provide a literal for a type that is inheritable during derivation. (Actually, there's another use for parameterless functions, and that is to move dependency on a literal value to a package body, so that if the value changes, you don't have to recompile the universe because a spec changed.) For example: generic ... package Sets is type Set_Type is private; function Empty_Set return Set_Type; ... end Sets; So during a derivation, I inherit the "literal" Empty_Set, because it's a primitive operation of the type. (It's also an example of how return type matters.) If Empty_Set had been declared as a (constant) object: type Set_Type is private; Empty_Set : constant Set_Type; ... then it wouldn't be inherited. (In that case, you'd have to declare another Empty_Set object.) > It could be worse though, as C++ demonstrates. I can't say, because I haven't done much C++ programming. The thing that's nice about C++ is that it's very flexible and powerful, allowing you to elegantly implement damn near any kind of abstraction, and using a natural syntax. (This is one fault of Ada: programmer defined types have a different syntax from built-in types.) However, this power and flexibility doesn't come for free, and the language is therefore hard to master. There are a lot of trap doors waiting for the unwary. > Next time I make a joke I'll make it about something that people don't > take so damn seriously. Death, famine, or nuclear winter maybe. :-) I didn't know it was a joke either, until you pointed it out! You have to realize that there are a lot of guys out there with strong negative opinions about Ada, who haven't actually done much (if any) Ada programming. They'd rather bitch about "problems with the language," instead of learning the idiom appropriate for their problem. This is too bad, because a lot of guys who think they hate Ada would probably love it, if only they took the time to learn it. Oh, well. Matt