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,f92fbb4a0420dd57 X-Google-Attributes: gid103376,public X-Google-Thread: 109fba,f92fbb4a0420dd57 X-Google-Attributes: gid109fba,public From: bobduff@world.std.com (Robert A Duff) Subject: Re: some questions re. Ada/GNAT from a C++/GCC user Date: 1996/03/29 Message-ID: X-Deja-AN: 144891746 references: organization: The World Public Access UNIX, Brookline, MA bcc: bobduff newsgroups: comp.lang.ada,comp.lang.c++ Date: 1996-03-29T00:00:00+00:00 List-Id: In article , Bill Newman wrote: >... (I hope against hope that > the tone of the answers will resemble the cross-language comparisons > in _Ada as a Second Language_ more closely than it resembles the > recent C++/Ada flamewar.) ;-) Did you notice the recent Eiffel/Ada flamewar? It was much more reasonable -- not even a flamewar, really, but a technical argument. It *is* possible! > Does GNAT completely implement generics as defined in the standard? > (I ask because I have heard that no compiler, G++ otherwise, has yet > implemented C++ templates completely, and the G++ implementation > caused me lots of hassles before 2.7.x, and still causes some hassles > now.) > > I *assume* that GNAT supports exceptions completely since they're an integral > part of the language and I didn't see any disclaimers, but since AFAIK G++ > doesn't do them very well, I'd like to double-check: how well does > GNAT do exceptions? Generics and exceptions are both standard parts of Ada, so anything that calls itself an Ada compiler implements them. Furthermore, these features have been around since Ada 83 (although a few bells and whistles were added in Ada 95). Any compiler can have bugs, of course. But the situation with C++, where templates and exceptions are pretty new, and not officially standardized, isn't true of Ada. > I didn't notice anything about garbage collection in the GNAT docs, so > I assume it doesn't support it. Will GNAT support GC in the > foreseeable future? GNAT does not support GC. I believe somebody is working on adding GC to GNAT. Also, the Intermetrics Ada compiler supports GC. > How well does GDB work with GNAT output? I've found it to be somewhat painful. However, it does work, and the Ada support is being improved. >...Is it possible to get GDB to > interactively call arbitrary procedures/functions from GNAT-generated > code? Yes, I do that a lot. > Is there any way in Ada to iterate abstractly over the contents of a > container,... Several ways: - Write a generic procedure Iterate, which takes a formal procedure representing the body of the loop -- that is, the formal procedure says what to do for each element. - Write a procedure that takes an access-to-procedure as a parameter. In standard Ada, this only works if the procedure being passed in is non-nested, which makes this technique fairly useless. In GNAT, you can "cheat", by using 'Unrestricted_Access, but that's not standard Ada. - Write an Iterator class. Define any particular loop by deriving from that, and overriding the Do_One_Element method, or whatever. I think there's an example in the Rationale. - For each data structure, define Start, Done, Current_Element, and Get_Next operations. Or some variation on this. None of these is as pretty as the Sather solution, but they achieve the main goal, which is to have the module that defines the data structure define how to loop, and the clients define what they do at each iteration. >... i.e. without writing each loop in a way which depends > strongly on the implementation of the container? I know I > could define a container class Foo_Basket_Type which would > let me do something like > for I = 1 .. Size(Foo_Basket) loop -- class implemented as array > Sum := Sum + Bletchery(Element(Foo_Basket, I)); > end loop; > or > I : Foo_Basket_Iterator_Type := Head(Foo_Basket); > .. > while not Is_Done(I) loop -- class implemented as list > Sum := Sum + Bletchery(dereference I); > I := Next(I); > end loop; It seems like the above does not have to imply "class implemented as list". So just write all your loops like that, whether the implementation is a list, or an array, or whatever. You can do this in most languages. This is what I was suggesting in the fourth item above. > but what I'd really like to is something like > for each I in Foo_Basket loop -- don't much care how class is implemented > Sum := Sum + Bletchery(dereference I); > end loop; > I understand that in languages like Sather, I could do this without > macros. In G++ I can come pretty close to this with CPP macros: > FOR_EACH(i, foo_basket) > sum += bletchery(*i); > In standard C++ (without the G++ `typeof' operator) I'd write something like > FOR_EACH(i, Foo_Basket::Iterator, foo_basket) > sum += bletchery(*i); > I use this idiom a lot -- about once per 40 lines of code in my > current project. I much prefer it to the alternative of making my > code depend on the implementation of the container. I don't agree that the only alternative to macros is to break the abstraction. The techniques I mentioned above also achieve your goal, although they are admittedly somewhat ugly. Macros can add syntactic sugar to those techniques, but don't fundamentally change things. >...Is there any way of > doing something like this in Ada? (I imagine there is, since the > alternatives look unnecessarily difficult to maintain.) I'll note that the alternatives I gave could be done in C++, too, without macros. > Due in part to my problems with GDB mentioned above, my C++ programs > tend to contain a lot of calls to macros MUTTER1, MUTTER2, > etc. defined either as no-op (for low levels of verbosity) or as > #define MUTTER1(x) do { cerr << mutter_prefix << x << '\n'; } while (false) > so that I can write things like > MUTTER1("done with sampling, w = " << w << ", table = " << table); > concisely. It's my impression that Ada's I/O facilities make it hard > to do trivial things like this concisely. Is this wrong? I don't really > want to have to write > if (Global_Verbosity >= 1) then > Write_Mutter_Indent(Global_Mutter); > Write(Global_Mutter, "done with sampling, W = "); > Write(Global_Mutter, W); > Write(Global_Mutter, ", table = "); > Write(Global_Mutter, Table); > Write_Line(Global_Error); -- silly typo encouraged by excess verbosity > end if; > for a simple statement like the one above. The usual thing is to write something like: Put_Line("done with sampling, W = " & Image(W) & ", table = " & Image(Table)); For simple data types, there is the 'Image attribute. For others, you have to write your own Image function, which converts it to a string. You said you wanted to do that anyway. If you don't like Text_IO.Put_Line, write your own: Two procedures for each verbosity level, and one of those two can add the new-line. Or maybe you always want a new-line. Or maybe the verbosity should be passed as a parameter, with a default. I can imagine many variations. Some people go even further, and define overloaded versions of "&" that do both the concatenation and the conversion-to-string. Seems like overkill to me, but some people like it. > When I make two different instantiations of a generic package with the > same arguments, I understand the compiler treats them formally as two > different packages, which is OK with me. However, I'd appreciate > knowing the compiler wouldn't actually output two redundant copies of > the corresponding (identical?) machine code, but instead share the > code. I saw somewhere that the compiler is given considerable freedom > to share one instantiation between several arguments if it thinks it's > appropriate, which is also OK with me. However, I haven't seen any > guarantee that the compiler won't output redundant copies for > instantiations with identical arguments. Is there such a guarantee? No. Some compilers do it, some don't. Currently, GNAT does not. If you want to avoid code bloat, you can usually arrange for *most* of the code in a generic to be separated out into a non-generic package. Sometimes, this requires low-level hacks, but the low-level hacks are hidden inside the generic and its helper package, so they need not concern clients. > My examples of `for each' and `mutter' above share a common feature: > I'd like to abstract away a common pattern so that I only need to type > each argument once, and I haven't figured out how to do it in Ada. Macros can be used to achieve that, but in many cases, so can procedures, generics, Sather's iterators, etc. IMHO, the more restricted features are generally better than macros. Macros are more powerful, but also more difficult to understand. TeX is a language that uses macros for just about everything, and I find TeX code to be totally incomprehensible, despite the fact that I've read the TeX Book 4 times. >...I > don't object to the rest of the verbosity I have seen in Ada: I can > see that it might make programs more readable, and since it's checked > by the compiler it shouldn't make programs significantly harder to > maintain. However, the kind of verbosity required to hand-code > patterns like `for each' and `mutter' does not seem to make code more > readable, and since it is not checked by the compiler I'm afraid it > might cause maintenance problems by making it possible to add bugs by > changing some but not all occurrences of the redundant terms. Are > there ways (e.g. in the cases described above, or other cases that I > haven't thought of yet) in which Ada will force me to do this kind of > thing where C++ wouldn't? If so, is this a bad thing (as I suspect), > or is it a positive feature in some sense that I haven't figured out, > or is it just the price we pay for the benefit of being guaranteed > that no one has abused macros in the program? I think the last phrase ("price we pay...") is accurate. I actually think macros would be nice to have in some rare cases. But in *most* cases, if you're using macros, that's an indication of a language flaw -- you didn't have a higher-level feature that got the job done, so you had to resort to macros. You'll find a lot of anti-macro fanatics in the Ada world. > Why doesn't Ada 95 allow declarations to be interspersed with ordinary > statements as C++ does? (Or does it? _Ada as a Second Language_ is a > big book!) It seems to me that the C++ approach is a small but > definite win. Does it interact very badly somehow with all those > guarantees on elaboration order? To intersperse declarations, you have to use a block_statement, like this: for I in Some_String'Range loop -- I wish I could easily use an iterator here ;-) declare X: constant Character := Some_String(I); begin ... end; end loop; To me, the "declare", "begin", and "end" are just useless verbosity. I prefer the C++ rule. As to WHY the Ada syntax is this way, I'm not sure. It's partly because it was inherited from Pascal. In Pascal, "begin" separates purely compile-time declarative stuff from purely run-time algorithmic stuff, so the "begin" makes some conceptual sense. But in Ada, the declarations can do all kinds of run-time stuff, so putting "begin" in between declarations and statements makes less sense. Pascal doesn't have block_statements, but the syntax of Ada's block_statements comes from the same Pascal heritage. The "begin" also makes a difference for exception handling and tasking. But I don't find these reasons compelling, either. > Someone remarked -- in this newsgroup recently IIRC -- that macros > were explicitly disallowed in the design goals for Ada (Ada 83?), > since they make programs hard to understand. I don't remember the > exact wording, but as I remember the key reason was that you would > never know what was a macro and what wasn't without reading the entire > program hunting for macro definitions. (I searched for `macro' in the > Rationale without success, so I'm just going on speculation and dim > memory here.) It seems to me that that is a funny objection: most > macro processors these days don't require a characteristic pattern to > introduce macro expansions, but as far as I can tell there's no reason > that you couldn't restrict macro expansion to e.g. patterns preceded > by the keyword `macro'. I don't think this is the main reason people think macros are hard to understand. As you say, it would be easy to design the syntax so that a macro invokation looks different from anything else. Macros are generally hard to understand for other reasons, I think. Part of the problem is the low-level character-based nature of macros. But as you point out below, that's not true of all known macro facilities. Part of the problem is the lack of scoping rules -- if a macro expansion refers to a global variable X, it's referring to whatever X happens to be lying around at the point of the macro expansion. Contrast that with Ada's generics, where the names are bound at the site of the generic itself. Part of the problem is that you have to imagine what the macro expands to in order to understand what it does, and complex code trasformations are hard to imagine accurately. TeX macros have additional problems, like the fact that you can't tell at the call site how many arguments are being passed -- in fact, you can't in general tell until run time. >... Granted, C's macro facilities are fairly > disgusting and inconsistent with the design goals of Ada, but it seems > to me a macro facility (or `generic syntax' facility?) more consistent > with Ada could be useful -- perhaps something closer to Scheme's > `hygienic macros' than to C's macro preprocessor. Some restrictions > (e.g. only allowing macros to expand into expressions, sequences of > statements, or sequences of declarations, and not into arbitrary textual > strings) and perhaps importing from Lisp the idea that macros are > transformations on patterns of language tokens and expressions, rather > than transformations on patterns of characters) could do a pretty > decent job of discouraging people from writing truly screwy macros I agree. Lisp macros are much less painful than character-level macros. > without forbidding useful macros like `For_Each' or (to generalize > `MUTTER') `Write_Sequence' (sending a bunch of items to the same > stream, and using the language's overloading facility to sort out > which version of the Write procedure to call for each). If you complain that you want macros to do For_Each, I'll answer by giving you iterators, not by giving you macros. Same for Write_Sequence -- better to use a procedure call than a macro call, IMHO. >...Would this > have been a good idea, except perhaps that the language is too > complicated already, or would this be a bad idea for some more > fundamental reason? I think most Ada people would say that macros inherently cause hard-to-understand code, and therefore should be abolished. I don't agree. I would like to have a macro facility (at a higher level than character-level), but I would use it rarely. > Finally, I found it intriguing when someone (somewhere in the endless > C++ vs. Ada thread) described a program which used exceptions to > convert runtime errors to `graceful degradation' so successfully that > the program (for fire control?!) continued operating more-or-less > correctly despite numerous bugs. ... I'm not impressed by that example. It's true that exceptions can be useful in designing a fault-tolerant system, but fault-tolerance is still very hard, and needs to be very carefully designed. For *most* programs, the primary purpose of exceptions, most of the time, is to detect bugs as early as possible, and the response is to fix the bug, not to allow the program to muddle along in a confused state. - Bob P.S. I hope you enjoy Ada. Why not get a copy of GNAT, and try it out?