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, MSGID_SHORT,REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 Xref: utzoo comp.lang.ada:2807 comp.sw.components:369 comp.object:249 comp.software-eng:2235 comp.lang.modula2:1825 comp.lang.misc:3618 Path: utzoo!attcan!utgpu!jarvis.csri.toronto.edu!rutgers!gatech!hubcap!billwolf%hazel.cs.clemson.edu From: billwolf%hazel.cs.clemson.edu@hubcap.clemson.edu (William Thomas Wolfe, 2847 ) Newsgroups: comp.lang.ada,comp.sw.components,comp.object,comp.software-eng,comp.lang.modula2,comp.lang.misc Subject: Re: Inheritance & limited private types Message-ID: <6845@hubcap.clemson.edu> Date: 21 Oct 89 19:39:05 GMT References: <48695@ricerca.UUCP> Sender: news@hubcap.clemson.edu Reply-To: billwolf%hazel.cs.clemson.edu@hubcap.clemson.edu List-Id: [For those people in newsgroups other than comp.lang.ada who are also seeing this article: this thread arose in the context of a discussion in comp.lang.ada regarding the addition of multiple inheritance to Ada as part of the current Ada 9X revision process.] >From chase@Ozona.orc.olivetti.com (David Chase): > I'm not well-versed in Ada, but people I've talked to ensure me that > Ada has something similar to the "opaque types" of Modula-2 and > Modula-3. Combining opaque types and multiple inheritance can lead > to some nasty problems. I don't have a solution; just a problem. OK, first I'll rephrase the problem in a way that Ada people will find easier to follow. David assumes that there are two uses for inheritance; the first is specialization, in which one would take the abstraction "vehicle" and provide additional operations in order to define abstractions such as "car" or "truck". The second is derivation, in which one would take an existing abstraction and shape it into a new abstraction, using the implementations provided by the old abstraction as appropriate and overriding operations with new implementations as necessary. > It's the second case, and overriding of existing message-method > bindings in general, that causes the problem. The overriding of the current implementation for only certain operations of a type does cause problems in that in general, a good implementation for a given data abstraction will take maximum advantage of the relationships between the various operations, thus creating an implementation in which the data structures and algorithms involved depend strongly on the fact that exactly that set of operations is to be supported. For example, a standard queue abstraction might be implemented by a singly-linked list structure in conjunction with a descriptor which points to the first and last elements in the list, in order to efficiently support only the operations Enqueue and Dequeue. But now suppose that we wish to support a priority queue instead, and we wanted to override the existing Dequeue operation with a new one. The new operation would scan the entire list in order to find the element having the highest priority, and then dequeue that element. Unfortunately, this is an O (n) algorithm, which is quite inefficient relative to the O (log n) algorithms which could be used if the new abstraction was supported by a binomial forest rather than a linked list. This represents the result of a tradeoff. By using inheritance in conjunction with the implementation of the old abstraction, we have traded off implementation quality in exchange for speed of implementation. The priority queue implementation obtained via the overriding of inherited operations is of very low quality, but this may or may not be an important consideration relative to the cost of providing a high-quality implementation. It has long been recognized that the time to worry about product efficiency is AFTER the product has been developed and put through a profiler to determine where the bottlenecks are in the system, since in this way the high cost of maximizing efficiency can be directed to the points at which it will do the most good. This principle extends to our derived priority queue implementation; if the profiler indicates that a software system is running with unacceptably slow speed due primarily to the time required to do priority queue operations, then a more sophisticated implementation of the priority queue which takes maximum advantage of the relationships between the set of required operations is probably a good idea. We then apply economic principles once again and try to find a software components supplier which will sell us a high-performance priority queue implementation, complete with formal specifications for each operation and timing results for typical workloads. In general, the components supplier will even be able to offer a number of different implementations, some of which will emphasize the particular operations which are most crucial to your system at the expense of others which are not as important. Now back to the problem under consideration... :-) > Suppose I have a type T1, and it has a message-method binding [...] Rephrasing, here is the problem: package Example is -- this is the specification type T2 is limited private; -- can't know or use the details procedure DO_SOMETHING_WITH (Parameter : in out T2); end Example; package body Example is -- this is the implementation -- type T1 has the operation OP1, and implementation IMP1. -- type T2 inherits operation OP1 and implementation IMP1 from T1. end Example; -- Now the user of package Example declares a new type, T3, -- which is_a T1 and also is_a T2. The user then specifies -- a new implementation, IMP2, for OP1. > I claim that (1) this should be legal, because the programmer has > no way of knowing that it should be illegal (information hiding, > right?) (2) An object O of type T3 should have the binding [OP1-IMP2] > where it is visible that O is a T3 (because that's what the programmer > said) (3) An object O of type T3 should have the binding [OP1-IMP1] > whenever it is in a context where it is known to be a T2, but not > known to be a T3 (because the programmer should not be able to > invalidate the correctness of a module if the internals of the > module are hidden). [In other words, if procedure DO_SOMETHING_WITH > is called, which expects a parameter of type T2, then IMP1 should be > used for the object of type T3 because it is expected to perform as > a T2 in that context.] [... C]hanges to the [implementation] bindings > for T1 by a subtype T3 *must not* change the behavior of T3 when > considered as a T2; any proofs about the behavior of a T2 would > thus go out the window (and the programmer would be clueless, because > the dependence of T2 on T1's [specification-implementation] bindings > is hidden). [...] > > I am *not* arguing that this should be the case if T2 is not opaque; > in that case everything is in the clear, and either an error message > or a change in T2's behavior is allowed. This nasty situation could > be avoided by creative prohibition (objects cannot be opaquely > exported -- yuck; opaque types cannot be inherited -- yuck; no > multiple inheritance -- many people say yuck, but that's what we live > with in Modula-3), but I'd be even happier if I could figure it out. First, let me give a reference to an important tutorial regarding the "synthesis of typing systems", as found in "Ada, an advanced language incorporating extensive support for various forms of abstraction", with "the powerful and flexible capabilities of OOP [object-oriented-programming]", which is entitled "Type Theories and Object-Oriented Programming" and can be found in ACM Computing Surveys, Vol. 20, No. 1, March 1988. Actual progress toward this objective is described by Wilf R. LaLonde in "Designing Families of Data Types Using Exemplars" (ACM Transactions on Programming Languages and Systems, Vol. 11, No. 2, April 1989), who argues that "designing families of data types is a programming-in-the- large problem", that "class-based systems need the notion of private types if they are to surmount their current limitations", and that "an advanced system should support the programming maxim that designers can switch implementations as they choose without impacting users. We show, using the implementation technique that we call programming by exemplars, that conventional class-based systems cannot help but violate the maxim. Exemplar-based systems support it easily.". Finally, I agree with each of your claims (1), (2), and (3), and in my opinion they are a natural consequence of the information hiding concept. As far as T3 is concerned, T2 does not HAVE any operations other than procedure DO_SOMETHING_WITH. Therefore, there can be no conflict between T3's inheritance of OP1 from T1, T3's provision of IMP2 for OP1, and T3's status as a T2 having the single operation DO_SOMETHING_WITH. The fact that T2 was declared internally to be an object having the operation OP1 as inherited from T1 is hidden from T3, and therefore is only of consequence within the package body which provides an implementation of T2 and its associated operation. Within that operation, we know that the parameter is of type T2, and therefore this "view" of T3 as a T2 will be supported. The T2 capabilities are visible to procedures such as DO_SOMETHING_WITH which have the needed authorization to view T2s, while also being hidden from T3 and from anything else which is external to the T2 implementation. Bill Wolfe, wtwolfe@hubcap.clemson.edu P.S. I will be in Pittsburgh during the week of October 23-27 for the Tri-Ada '89 conference, and therefore will not be able to respond to any followups until at least October 30th.