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=0.7 required=5.0 tests=BAYES_00,INVALID_DATE, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,38af5710cd0592fa X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 1994-11-21 09:52:27 PST Path: nntp.gmd.de!xlink.net!howland.reston.ans.net!swiss.ans.net!newsgate.watson.ibm.com!watnews.watson.ibm.com!ncohen From: ncohen@watson.ibm.com (Norman H. Cohen) Newsgroups: comp.lang.ada Subject: Re: Motivating inheritance and dyn. poly. Date: 21 Nov 1994 15:33:03 GMT Organization: IBM T.J. Watson Research Center Distribution: world Message-ID: <3aqejf$o55@watnews1.watson.ibm.com> References: <3ai9g8$5e6@israel-info.datasrv.co.il> Reply-To: ncohen@watson.ibm.com NNTP-Posting-Host: rios8.watson.ibm.com Date: 1994-11-21T15:33:03+00:00 List-Id: In article <3ai9g8$5e6@israel-info.datasrv.co.il>, benari@zeus.datasrv.co.il (Moti Ben-Ari) writes: |> I believe that the normative use of variants is to represent |> true alternates, e.g. a message which can take dozens of forms. |> On the other hand, many try to use it to save a couple of words |> of memory as if we were still programming for the dear, |> departed 16K PDP-11. |> |> The example in the Highlight section of the Rat. is typical. |> There is no reason why a simple record could not be used: |> |> type Alert is |> record |> P: Priority; |> Time_of... |> Message... |> Action... |> Ring... |> end; |> |> with null values or pointers used for non-relevant fields. |> Processing can be done using __non-nested__ if's or case's: |> |> if (A.Priority = Medium) or (A.Priority = High) then |> ... |> end if; |> if (A.Priority = High) then |> Display... |> Set_Alarm... |> end if; |> |> This is trivial to understand and maintain, and the time and |> space overhead is minimal. I agree that the example in Section I-2.3 of the 9X Rationale is peculiar. It has a tall, thin inheritance hierarchy (with branching factor 1): Base_Alert | Low_Alert | Medium_Alert | High_Alert Technically, this IS equivalent to an Ada-83 variant type, but a rather ugly one: type Alert_Tag is (Base_Alert, Normal_Alert, Medium_Alert, High_Alert); type Alert (Tag: Alert_Tag) is record case Tag is when Base_Alert => null; when Normal_Alert .. High_Alert => Time_Of_Arrival : Calendar.Time; Message : Text; case Tag is when Medium_Alert .. High_Alert => Action_Officer: Person; case Tag is when High_Alert => Ring_Alarm_At: Calendar.Time; when others => null; end case; when others => null; end case; end case; end record; However, I think the flattened alternative Moti proposes is far worse. I've maintained code (not written in Ada) that uses "implicit variants" of this sort, and it's a nightmare. The code provides no clear clue that a structure comes in a number of forms, that certain components determine which form a given instance of the structure holds, and that certain components only have meaningful values in certain forms. As the program was modified over the years by people who did not understand the implicit variant structure as well as the original programmers understood it, we find that code to copy an instance of the structure would copy even the currently inactive components, to be on the safe side. We find bugs that were introduced by programmers apparently assuming that since the structure appeared to be initialized, all of its components must have meaningful values. These bugs were repaired by fixing the most proximate symptom--stuffing a value into a component that belongs to a logically inactive variant, thus destroying the original programmers' notion of distinct variants and creating a much more complicated set of rules about when components have meaningful values. Inevitably, programmers needing to add more components to the structure for use during a particular phase of the program noticed that certain components were unused during that phase of the program, and decided that rather than waste storage, they would "reuse" the existing component, using the same component (or, worse yet, a differently typed component overlaid with that component) for a completely different purpose. This made it great fun trying to figure out what kind of data was held in a particular component at a particular point in the program. It would have been much better if the original programmer had written an Ada record with a variant part. A particular component of the record would have been clearly identified as a discriminant, it would have been clearly documented that the record has a finite number of forms, and the variant part would clearly have indicated which components are present in which forms. A maintenance programmer trying to reference a component in an inactive variant would have been alerted by a Constraint_Error (or perhaps even a compile-time warning) that he was doing something fundamentally meaningless, and would have redesigned his modifications accordingly rather than breaking the original data-structure design. The compiler would automatically take care of storage mapping, overlaying a new component for one variant with storage used for other components in other variants, while allowing the programmer to remain oblivious to the overlay and preserving the logical view that the new component is distinct from all other components. I short, I am firmly convinced that explicit variants controlled by discriminants are a signficant advance over flat records in which the current validity of each component is controlled by programmer convention. Returning to Moti's request for a more convincing example of type extension, consider streams, introduced Section 13.13 on RM9X. Ada.Streams.Root_Stream_Type is an abstract type with Read and Write operations. Streams are, from an abstract point of view, sinks and sources of raw binary data. Among the possible "variants" of streams are streams implemented as files, streams implemented as in-memory buffers, and streams implemented as communications ports. Packages Ada.Streams.Stream_IO and Ada.Text_IO.Text_Streams provide functions that offer access to a stream corresponding to an open file; although the implementation of these streams is not specified by the RM, a stream implemented as a file seems to be the only reasonable choice. The Distributed Systems Annex package System.RPC defines an extension of Root_Stream_Type named Params_Stream_Type, most likely implemented as a memory buffer or, on a distributed system without shared memory, as a communications port. The strength of the OOP approach for streams lies in the fact that one can write subprograms with Root_Stream_Type'Class parameters using only the abstract Read and Write operations of streams, without regard to how streams are implemented. (Most notable among these are the T'Read and T'Write subprograms for translating complex data structures, perhaps containing access values, to and from streams of bytes whose meaning is independent of the addresses implied by the access values.) Furthermore, if other implementations of streams are later conceived, new extensions of stream types can be declared with versions of Read and Write that are implemented accordingly. All software previously designed to work with streams will then work with the new kind of stream. Calls on Read and Write from within this software will automatically be "dispatched" to the versions of these procedures for the "variant" of streams currently being manipulated. Tagged types are, in effect, like Ada-83 types with variants in which the case statements examining discriminants are implicit in dispatching calls and in which new variants can be added without modifying old source. -- Norman H. Cohen ncohen@watson.ibm.com