comp.lang.ada
 help / color / mirror / Atom feed
From: "Randy Brukardt" <randy@rrsoftware.com>
Subject: Re: Studying and Maintaining GNAT, Is There Any Interest in a New Group?
Date: Thu, 30 Aug 2018 18:25:48 -0500
Date: 2018-08-30T18:25:48-05:00	[thread overview]
Message-ID: <pm9uds$hpu$1@franka.jacob-sparre.dk> (raw)
In-Reply-To: pm87ue$ifg$1@gioia.aioe.org

"Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> wrote in message 
news:pm87ue$ifg$1@gioia.aioe.org...
> On 2018-08-29 23:43, Randy Brukardt wrote:
>> "Dmitry A. Kazakov" <mailbox@dmitry-kazakov.de> 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.


  reply	other threads:[~2018-08-30 23:25 UTC|newest]

Thread overview: 55+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-08-25 12:27 Studying and Maintaining GNAT, Is There Any Interest in a New Group? patrick
2018-08-25 13:56 ` Dan'l Miller
2018-08-25 16:00   ` patrick
2018-08-25 16:16 ` Luke A. Guest
2018-08-25 17:42   ` patrick
2018-08-25 19:25     ` Simon Wright
2018-08-25 20:24       ` patrick
2018-08-25 21:48         ` Luke A. Guest
2018-08-25 21:53           ` patrick
2018-08-25 22:05             ` Luke A. Guest
2018-08-26 19:54           ` Dan'l Miller
2018-08-26 20:14             ` Dan'l Miller
2018-08-26 22:52             ` Lucretia
2018-08-27  2:38               ` Dan'l Miller
2018-08-27 14:46                 ` Lucretia
2018-08-27 15:42                   ` Dan'l Miller
2018-08-27 21:27               ` Randy Brukardt
2018-08-28  7:26                 ` Dmitry A. Kazakov
2018-08-29  0:16                   ` Randy Brukardt
2018-08-29  8:20                     ` Dmitry A. Kazakov
2018-08-29 21:43                       ` Randy Brukardt
2018-08-30  7:55                         ` Dmitry A. Kazakov
2018-08-30 23:25                           ` Randy Brukardt [this message]
2018-08-31  8:48                             ` Dmitry A. Kazakov
2018-08-31 22:42                               ` Randy Brukardt
2018-09-02  8:02                                 ` Dmitry A. Kazakov
2018-09-04 22:18                                   ` Randy Brukardt
2018-08-29  3:02                 ` Paul Rubin
2018-08-29  6:18                   ` Luke A. Guest
2018-08-29 19:00                     ` Paul Rubin
2018-08-30  5:54                       ` Luke A. Guest
2018-08-30  6:29                         ` Paul Rubin
2018-08-27 21:18             ` Randy Brukardt
2018-08-27  9:37           ` Simon Wright
2018-08-27 16:54             ` Bill Findlay
2018-08-27 17:42               ` Shark8
2018-08-31 21:23                 ` Robert A Duff
2018-08-31 22:51                   ` Randy Brukardt
2018-09-01 19:42                     ` Robert A Duff
2018-09-02  8:04                       ` Dmitry A. Kazakov
2018-09-02 10:11                     ` AdaMagica
2018-09-02 12:10                       ` Jeffrey R. Carter
2018-09-02 14:30                         ` AdaMagica
2018-09-04 22:05                           ` Randy Brukardt
2018-09-01  7:41               ` Simon Wright
2018-09-01 17:27                 ` Bill Findlay
2018-08-27 17:35         ` Shark8
2018-08-25 21:17       ` Luke A. Guest
2018-08-25 23:16       ` Paul Rubin
2018-08-26  8:03         ` Rene
2018-08-26 10:09         ` Simon Wright
2018-08-25 16:43 ` Jeffrey R. Carter
2018-08-25 17:38   ` patrick
2018-08-25 17:39     ` Luke A. Guest
2018-08-25 17:45       ` patrick
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox