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=unavailable autolearn_force=no version=3.4.4 Path: eternal-september.org!reader01.eternal-september.org!reader02.eternal-september.org!feeder.eternal-september.org!news.albasani.net!newsfeed.xs3.de!io.xs3.de!news.jacob-sparre.dk!franka.jacob-sparre.dk!pnx.dk!.POSTED.rrsoftware.com!not-for-mail From: "Randy Brukardt" Newsgroups: comp.lang.ada Subject: Re: Studying and Maintaining GNAT, Is There Any Interest in a New Group? Date: Thu, 30 Aug 2018 18:25:48 -0500 Organization: JSA Research & Innovation Message-ID: References: <309225242.556906218.575482.laguest-archeia.com@nntp.aioe.org> <2145221813.556924687.162377.laguest-archeia.com@nntp.aioe.org> <3892c779-2924-405c-b88d-19389fc5ba3e@googlegroups.com> <1ceec6d8-c5c4-49b1-9808-a3580bba3f8e@googlegroups.com> Injection-Date: Thu, 30 Aug 2018 23:25:49 -0000 (UTC) Injection-Info: franka.jacob-sparre.dk; posting-host="rrsoftware.com:24.196.82.226"; logging-data="18238"; mail-complaints-to="news@jacob-sparre.dk" X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 6.00.2900.5931 X-RFC2646: Format=Flowed; Response X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.7246 Xref: reader02.eternal-september.org comp.lang.ada:54300 Date: 2018-08-30T18:25:48-05:00 List-Id: "Dmitry A. Kazakov" wrote in message news:pm87ue$ifg$1@gioia.aioe.org... > On 2018-08-29 23:43, Randy Brukardt wrote: >> "Dmitry A. Kazakov" wrote in message ... >> Surely you "know" them; my point is that there is an incredible amount of >> busy-work needed to write them (as opposed to just modifying the handful >> of >> places that need that). >> >> For instance, one has to write tree-walking code into every override, as >> there is no good way to do that with inheritance (the override has to >> make a >> dispatching call on itself on the child mode(s)). The only way to do that >> with though inheritance is to move all of the tree-walking stuff outside >> of >> the OO operations (as is done in the tree container) -- but then you are >> no >> longer using strong typing on the children -- the number and kinds of the >> children have to be checked with manual code. > > This I don't understand. Why tree operations cannot be primitive and/or > class-wide? They are primitive. That's what forces writing the traversal code into every operation. Consider the following node: type Root_Node is abstract tagged null record; type Expr_Tree_Access is access Root_Node'Class; procedure Fold (Expr : in out Root_Node); -- Fold the tree. type Binop is new Root_Node with record Kind : Op; Left, Right : Expr_Tree_Access; end record; and the implementation of the Folding operation: overriding procedure Fold (Expr : in out Binop); procedure Fold (Expr : in out Binop) is begin Fold (Expr.Left.all); Fold (Expr.Right.all); -- If Expr.Left and Expr.Right are constants, calculate the folded value. end Fold; This operation has to do recursive (dispatching calls) on the children. And so does every other such operation that gets defined (dozens). You can't write such code separately from the recursive calls without completely losing any typing. (You have to resort to generic formal subprograms or access-to-subprograms to do that, which are very weakly typed techniques.) This gets much more complicated for items that have lists or worse lists-of-lists (like an Ada "name"). > As a general observation, anything you can do with subtypes of a single > type you can also do with a class of a types hierarchy. [There could be > some obstacles regarding indefinite vs definite types, but they usually > apply to both designs.] This is true, you just have to write 3 times as much code to make the OO design work. And you lose the ability to do agile programming, because you have to implement so much new code to add a new node or operation (probably on the order of a week for either in an Ada compiler perspective -- dozens of operations and dozens of node types are needed there). >>>>> Since nothing of that kind is possible, what you propose is monolithic >>>>> design AKA "god-class", when the variant record must know *in advance* >>>>> all >>>>> future "extensions", which are extensions no more. >>>> >>>> Why in advance? One *modifies* code in maintenance; one doesn't tie >>>> both >>>> hands behind your back! It's hard enough to work on a compiler without >>>> making arbitrary rules preventing the modification of types. >>> >>> That is the exact meaning of "in advance": before the final version of >>> the >>> program unit gets ready all variants must be known. >> >> Umm, we're talking about a compiler, and there never is a "final" version >> of >> a compiler. > > Final = deployed, of course. Which is relevant how? It's just a snapshot of a continual evolution. ... >>> Note that inheritance would handle this safely. It will be: >>> >>> Current.Token.Do_Something; -- A dispatching call override >>> >>> Do_Something would be abstract must-be-overridden for Float_Token. >> >> Sure, but that's what's maddening about using OOP in this way. You have >> to >> define and implement *every* such operation that way for a new node >> before >> anything can be done (even compiling the changed program). > > Exactly. This is called being typed. Operations do not exist without > types. The design with case-statements spread all over the client code is > untyped because chunks of client code under "whens" are not attributed to > any type, have no contract etc. OO allows us > > 1. To give a contract to this code > 2. To refactor and reuse this code > 3. To place it in separate compilation units *related* to the types > involved. (1) There is no useful contracts for individual nodes -- all of the contracts with any meaning are on the tree as a whole. And it is the state of the nodes, rather than their kinds, which figure mainly into those contracts. (The state being part of the data, not part of the types.) (2) Refactoring isn't any harder with a variant design than it is with a OO design -- I do those sorts of things all of the time. (But not too often, one has to be pretty sure before doing any refactoring that it really will benefit the program -- a lot of the time, such things end up being lateral moves where one problem is fixed, but only by introducing others. Ada, like almost anything real, is not that regular!) (3) We abandoned this at the very beginning of Janus/Ada development -- the reason being that we were running in extremely space limited environments, and we couldn't afford to use extra calls in the code needed for any sort of encapsulation. (Similarly, we used a lot of global variables in early Janus/Ada because they were substantially cheaper the access than local variables on the Z80 machines. We knew better, but couldn't afford to do it right.) While the global variable thing is surely worth stamping out (most of them are gone now, a few persistent ones that are heavily used remain), I'm unconvinced that adding a lot of Ada "goodness" would really work in an Ada compiler environment. You would need so many getter/setter routines that you would be spending 50% of your time adding/fixing them rather than doing anything useful. We've gotten a lot of mileage out of using aggregate assignments to ensure completeness rather than trying to make everything private. As I said that the outset, I'm not sure what the right answer is, or even if there is a right answer. >> There are likely >> to be hundreds of such operations for a compiler (certainly dozens). That >> is >> very much like monolithic waterfall development -- there is nothing agile >> about it. > > Here is where MI comes into play. These hundreds of operations belong to > different interfaces. Tree manipulation and node semantics must be > described separately. As noted above, I don't think that is possible. I certainly couldn't find any way to do it in the Claw Builder (other than using generic formals to to tree walkers -- and generics don't inherit). >> I try to keep the compiler compiled almost all of the time when doing >> development, even when making major changes like new node kinds. >> Compile-time errors tend to get eliminated first. That's in part because >> code that isn't compilable shouldn't be checked in, and moreover we >> always >> need to be able to make fixes for customers. The time while the compiler >> isn't compilable is a risk period. >> >> If instead you use some tool to make an empty set of TBDs for all of >> these >> routines, now you've totally eliminated any compiler-based help (all of >> the >> needed routines exist, they just don't *work*) and you have exactly the >> same >> need for testing that you would have had using a variant. So you've >> gained >> nothing. > > Morale, do not use crappy tools. Somebody might someday come with a tool > that would generate all "whens" in all case statements filled with null- > or raise-statement. Would that be a failure of Ada case-statement design? > No. If you do that mess on language level (see dynamic predicates), that > is the language mess. If you do mess with a tool, well, that is no > language problem. Which does not answer the underlying question: how do you do agile development if adding an operation or new kind of node requires 4 days of coding? ... >>> It helps massively, especially interfaces, of course only when used >>> properly during analysis and design phase in order to describe >>> abstraction. One should use the strengths of typing instead of working >>> around them. Variant records in place of interfaces is a path leading to >>> weak typing. I don't say that one should never use variant records, I >>> say >>> that one should not misuse them. >> >> I use strong typing to ensure that nodes don't get mixed with symbols or >> with memory allocation record or many other things. > > But you allow mixing nodes freely? That is the problem. In a tree all > nodes are same, but there is semantics of what the node represents and you > allow mixing that semantics without mapping it onto types. Only one aspect > of your design is strongly typed: tree manipulations, BTW, the aspect more > or less marginal to computer design. For Ada at least, the only meaningful data structure is the expression tree. We don't care about the individual nodes outside of a handful of tree walking operations. Most everything talks specifically about the tree. The tree does have different states -- but all of the nodes in the tree ought to be in the same state. One could imagine trying to encode the state into the types, but that would require copying/relinking nodes every time there is a state change (since Ada doesn't allow changing tags in existing objects). That would be a massive overhead, and since much of the cost of a compiler is managing nodes (allocation/deallocation/copying/reading/writing), the last thing you would want to do is increase that overhead. >> I don't see any point in >> treating nodes as different types. Spending a lot of extra time doing >> analysis to try to bring structure where there is little natural >> structure >> is wasteful at best. > > There might be categories of nodes sharing certain properties and in the > end some refactored code. You cannot describe such things without some > form of interface, dynamic (tagged) or static (generics). Well, maybe you can't, but I do it all of the time. :-) Maybe I could do it a bit more often, but as I said above, most refactoring is just a waste of time (swapping known problems for new ones). It takes a lot of disipline not to waste time doing such things. In any case, I don't expect ever to agree with you on these points. I think a lot of people take strong typing too far -- but of course the sweet spot is hard to find. There may be an argument for going further than I do, but my experience doesn't show much value to that. Randy.