hoelzle@Neon.Stanford.EDU (Urs Hoelzle) (07/27/90)
While reading Meyer's OOSC I stumbled over this (section 11.5, "Inheritance and Information Hiding"): "The Eiffel answer is quite simple [..] the two mechanisms are completely orthogonal." I can see how you can claim this in a dynamically-typed language, but I don't understand what `Information Hiding (IH)' means in a statically typed language like Eiffel if the two mechanisms (inheritance and IH) are indeed orthogonal. IMHO you can no longer (statically, i.e. at compile time) enforce IH in this case. Consider this: class A exports feature f class B (a subclass of A) does not export f That is, if a is of type A and B of type B, a.f is legal, but b.f is not. Now consider a code sequence such as a := <some expression yielding a value of type A *or* B>; a.f The invocation of feature f is *only* legal if the object pointed to by a is actually an instance of class A. If it were an instance of class B, we would illegally invoke a "hidden" feature, violating the contract metaphor and the assertion system. It is certainly possible to check for this at run time but not at compile time. That is, given the assumption that inheritance and IH are orthogonal, IH semantics cannot be enforced statically. In fact, Eiffel is not type-safe if we consider IH violations to be type errors. Am I missing something or is this indeed a problem? -Urs
bertrand@eiffel.UUCP (Bertrand Meyer) (07/27/90)
From <1990Jul26.221405.23439@Neon.Stanford.EDU>
by hoelzle@Neon.Stanford.EDU (Urs Hoelzle):
> Am I missing something or is this indeed a problem?
Yes.
I mean: yes, there is a problem and yes, you are missing
something, if only previous discussions on this newsgroup.
The issue was discussed at length on at least two occasions
(July 1989 and last March). Anyone who has access to
archives of the newsgroup can go to my March posting
<265@eiffel.UUCP>, which includes references to many
earlier messages by various people.
--
-- Bertrand Meyer
bertrand@eiffel.com
carroll@udel.edu (Mark Carroll <MC>) (07/27/90)
In article <1990Jul26.221405.23439@Neon.Stanford.EDU> hoelzle@Neon.Stanford.EDU (Urs Hoelzle) writes: ]While reading Meyer's OOSC I stumbled over this (section 11.5, ]"Inheritance and Information Hiding"): ] ] "The Eiffel answer is quite simple [..] the two mechanisms are ] completely orthogonal." ] ]I can see how you can claim this in a dynamically-typed language, but ]I don't understand what `Information Hiding (IH)' means in a ]statically typed language like Eiffel if the two mechanisms ](inheritance and IH) are indeed orthogonal. IMHO you can no longer ](statically, i.e. at compile time) enforce IH in this case. ] ]Consider this: ] ] class A exports feature f ] class B (a subclass of A) does not export f ] ]That is, if a is of type A and B of type B, a.f is legal, but b.f is ]not. Now consider a code sequence such as ] ] a := <some expression yielding a value of type A *or* B>; ] a.f ] ]The invocation of feature f is *only* legal if the object pointed to ]by a is actually an instance of class A. If it were an instance of ]class B, we would illegally invoke a "hidden" feature, violating the ]contract metaphor and the assertion system. ] ]It is certainly possible to check for this at run time but not at ]compile time. That is, given the assumption that inheritance and IH ]are orthogonal, IH semantics cannot be enforced statically. In fact, ]Eiffel is not type-safe if we consider IH violations to be type errors. ] ]Am I missing something or is this indeed a problem? ] I think that you are missing something. It IS possible to check this statically. If you define a taglist for each feature in the system, consisting of the list of features called for each argument used in the proceedure, making this a recursive process (that is, for routine A, include the features used directly in A, and the features used by each routine called by A), you have a total list of features used for each argument to the routine. When you make use of any instancye of class A, you consult this taglist, to insure that every feature used in exported by the class. It's certainly not easy or fast, but it is possible. (I do not have ANY idea if this is what Eiffel does!) ]-Urs <MC> -- |Mark Craig Carroll: <MC> |"We the people want it straight for a change; |Soon-to-be Grad Student at| cos we the people are getting tired of your games; |University of Delaware | If you insult us with cheap propaganda; |carroll@dewey.udel.edu | We'll elect a precedent to a state of mind" -Fish
kaplan@scooby.cs.umass.edu (Alan Kaplan) (07/27/90)
In article <382@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
...
The issue was discussed at length on at least two occasions
(July 1989 and last March). Anyone who has access to
archives of the newsgroup can go to my March posting
<265@eiffel.UUCP>, which includes references to many
earlier messages by various people.
Can you point out how/where you can access these archive? Thanks!
Regards,
Alan
kaplan@cs.umass.edu
--
Regards,
Alan
kaplan@cs.umass.edu
hoelzle@Neon.Stanford.EDU (Urs Hoelzle) (07/27/90)
bertrand@eiffel.UUCP (Bertrand Meyer) writes: >From <1990Jul26.221405.23439@Neon.Stanford.EDU> >by hoelzle@Neon.Stanford.EDU (Urs Hoelzle): >> Am I missing something or is this indeed a problem? >Yes. >I mean: yes, there is a problem and yes, you are missing >something, if only previous discussions on this newsgroup. >The issue was discussed at length on at least two occasions >(July 1989 and last March). Anyone who has access to >archives of the newsgroup can go to my March posting ><265@eiffel.UUCP>, which includes references to many >earlier messages by various people. Could someone please mail me these articles? aTdHvAaNnKcSe, -Urs
bertrand@eiffel.UUCP (Bertrand Meyer) (07/30/90)
In <1990Jul27.163928.5886@Neon.Stanford.EDU>, hoelzle@Neon.Stanford.EDU (Urs Hoelzle) asks > Could someone please mail me these articles [about problems in > Eiffel type checking]? In <KAPLAN.90Jul27123145@scooby.cs.umass.edu> Alan Kaplan asks whether there is any publicly available archive of comp.lang.eiffel. Although Interactive Software Engineering has a complete (we think) record of the newsgroup, we are not able to provide a publicly available archive (through anonymous FTP or something of the sort), and most likely will not be in the near future. This is regrettable since there have been many insightful postings on topics that keep coming up in queries made by people who become interested in Eiffel and related issues. It is hard for us to keep on mailing individual copies of old postings to many people. I have been thinking of preparing a printed ``comp.lang.eiffel's greatest hit'' collection, but this is a somewhat lower priority project, which also involves asking posters' permission and other hassles. If any university or other institution is equipped and willing to support a publicly available archive of future postings, we will be glad to provide it with a full copy of our records of the newsgroup, to make sure that the archive is complete. In the meantime, since questions about the type system come up so frequently, and my July 1989 article, not publicly available elsewhere, is the least unsatisfactory answer I can give, I am reposting it, together with a companion article on ``Eiffel types''. See next three postings. -- Bertrand Meyer bertrand@eiffel.com
chrisv@runx.oz.au (Chris Velevitch) (01/24/91)
I disagree with allowing a class to access inherited features that are not exported. It does not make sense that secret features are known to heir of a class. If a feature is not publicly known, then how can you know about the feature to use in the descendent class. Eiffel allows a class to be designed so it has no interface, which can then be used in descendent classes. What use is a black box in which you cannot put anything in or take anything out. When designing a class, a designer makes the interface public, not its implementation. The user of a class buys services from the class based solely from its publically known behaviour and features. You can only inherit features that are known. I consider inheritance as a form of buying services. -- Chris Velevitch RUNX Unix Timeshare | Internet: chrisv@runxtsa.runx.oz.au Sydney NSW, Australia. | UUCP: uunet!runxtsa.runx.oz.au!chrisv
barmar@think.com (Barry Margolin) (01/25/91)
In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes: >I disagree with allowing a class to access inherited features that are >not exported. It does not make sense that secret features are known to >heir of a class. If a feature is not publicly known, then how can you >know about the feature to use in the descendent class. I like C++'s solution to this, the private/protected/public distinction. The private members of a class are only visible within the class, internal to the "black box". Protected members are visible to derived classes; frequently class derivation is used to extend behavior, in which case it is reasonable to allow a more intimate relationship between the base class and the subclasses, and protected members can be used for this. And public members are fully exported. The applicability of information hiding ideas can depend heavily on the design of the OO language. For instance, it's hard to provide this could be done in CLOS, because there is no notion of methods "belonging" to a particular class, due to its support of generic function dispatch based on the types of multiple arguments. I suppose there could be rules specifying that WITH-SLOTS and WITH-ACCESSORS may only be used on specialized parameters, and may only specify slots visible within the class named in the parameter specializer. This would support information hiding with regard to data, but it's not clear how to extend it to procedures. -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
tynor@hydra.gatech.edu (Steve Tynor) (01/26/91)
>In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes: >>I disagree with allowing a class to access inherited features that are >>not exported. It does not make sense that secret features are known to >>heir of a class. If a feature is not publicly known, then how can you >>know about the feature to use in the descendent class. > >I like C++'s solution to this, the private/protected/public distinction. >The private members of a class are only visible within the class, internal >to the "black box". Protected members are visible to derived classes; >frequently class derivation is used to extend behavior, in which case it is >reasonable to allow a more intimate relationship between the base class and >the subclasses, and protected members can be used for this. And public >members are fully exported. In practice, I've found C++ `private's to be overly restrictive and in violation of the rule "it is not the buisiness for a class to decide how it may be extended in the future" (paraphrased from OOSC). It's another example of a C++ feature that actually reduces the reusability of C++ classes (non-virtual member functions being the other obvious one). =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= C++: Just say no. Steve Tynor Georgia Tech Research Institute Artificial Intelligence Branch tynor@prism.gatech.edu
bertrand@eiffel.UUCP (Bertrand Meyer) (01/26/91)
From <1991Jan23.224203.3206@runx.oz.au> by chrisv@runx.oz.au (Chris Velevitch): > I disagree with allowing a class to access inherited features that are > not exported. It does not make sense that secret features are known to > heir of a class. If a feature is not publicly known, then how can you > know about the feature to use in the descendent class. > > Eiffel allows a class to be designed so it has no interface, which can > then be used in descendent classes. What use is a black box in which you > cannot put anything in or take anything out. This has come up before, of course. There is little to add to Steve Tynor's response. Let me, however, reproduce an extract from a paper called ``Static Typing in Eiffel'', of which a preliminary version was posted about two years ago. The current version is part of a book called ``An Eiffel Collection'', which is a collection of Eiffel-related articles, published by Interactive. The text is an ASCII-equivalent of something meant for typesetting, with heavy use of italics, boldface and some graphics. The general ideas, however, should survive e-mail translation. - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Extracted from ``Static Typing for Eiffel'' In: An Eiffel Collection Published by Interactive Software Engineering, 1991. Reference TR-EI-20/EC. Copyright B. Meyer, 1991 - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 4.1 Rationale for the export rule Consider first the export-inheritance rule, which often surprises newcomers as an apparent violation of information hiding principles. How can a descendant override export restrictions of an ancestor, or hide what the ancestor exported? To obtain a better perspective on this rule it is useful to examine its application to what may be the archetypal example of inheritance. Assume PARENT is a class POLYGON and HEIR is a class RECTANGLE. The inheritance relation in this case is clear and natural. It is not absurd, however, to assume that class POLYGON has a procedure add_vertex (new: POINT) is -- Insert new vertex new at current cursor position do ... ensure nb_vertices = old nb_vertices + 1 end -- add_vertex For general polygons, this procedure may make sense. It is not, however, applicable to rectangles, whose class invariant should contain the clause nb_vertices = 4 The Eiffel solution is to ensure that class RECTANGLE does not export procedure add_vertex. This is the kind of case that led to situations such as [REFERENCE TO EARLIER EXAMPLE OF ERRONEOUS FEATURE CALL OF THE FORM x.f], with f being add_vertex. |- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ---| | | | | | (Sorry, no graphics available for e-mail) | | | |(Please draw a figure with a bubble labeled POLYGON at the center | |top; below it, bubbles labeled FIXED_POLYGON and VARIABLE_POLYGON, | |with a single arrow from each of these bubbles to the top one; | |below FIXED_POLYGON, a bubble labeled RECTANGLE, with a bubble | |to FIXED_RECTANGLE.) | | | | | | Figure 1: Alternative inheritance structures | | | - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -| Three policies are possible in the face of such cases. + Policy 1 considers that this use of inheritance is inadequate: RECTANGLE is not a subtype of POLYGON because not all operations applicable to the latter are applicable to the former. According to this policy, the proper solution (see figure 1) is to consider two heirs to POLYGON: FIXED_POLYGON and VARIABLE_POLYGON. Procedure add_vertex is a feature of VARIABLE_POLYGON only, whereas RECTANGLE inherits from FIXED_POLYGON. This way the Eiffel rule can be changed to require that every class should export all features exported by its ancestors. (Even with this policy, however, there is no reason to prohibit a class from exporting a feature hidden by a proper ancestor. This technique is commonly used in practical Eiffel programming and, as discussed above, entails no danger of type failure.) + Policy 2 considers that inheritance is used properly in this case, but only for half of its capacity: as a module enrichment mechanism, not as a subtyping facility. The definition of class RECTANGLE should be permitted, but not polymorphic assignments of the form p := r (with p of type POLYGON and r of type RECTANGLE). With such a policy the conformance rules governing such assignments, as outlined in section 11.3.1 of ``Object-Oriented Software Construction'' (Prentice- Hall, 1988) and given fully in ``Eiffel: The Language'' (Interactive Software Engineering, 1989, and Prentice-Hall, 1991), would have to be updated to exclude such cases. If polymorphic assignments are needed, then the inheritance structure should be redesigned as shown in figure 1. + Policy 3 is more liberal: it permits the above polymorphic assignments as long as it can be ascertained statically at reasonable cost, for any Eiffel system involving these classes, that no type failure may result; in other words, that no add_vertex operation may ever be applied to p or an entity that may become associated with p. This is the policy applied in Eiffel, for which the exact rules will be given below. What speaks in favor of policies 1 and 2 is that they are extremely easy to implement for the compiler writer. Restricting exports in descendant classes (policy 1) is trivial; restricting polymorphic aliasing (policy 2) is almost as immediate. These solutions would also have the advantage of quieting the theoreticians and removing the fears of prospective users. In short, all obvious arguments seemed to push the designers of Eiffel towards policy 1 or 2: convenience (since the same group is also in charge of an implementation) and ease of convincing future users. So it takes some dose of fortitude to choose policy 3 and stick to it. Why should this be the Eiffel policy? The reasons can only be understood by going beyond the purely theoretical discussions and considering the practice of object-oriented software construction. Policies 1 and 2 assume that programming is for gods. Gods never make any mistake. Those among them who aside from their other business indulge in the heavenly pleasures of object- oriented programming always get their inheritance structures right the first time around. In today's job market, however, most employers must resort to hiring programmers who are only demigods, or even in some cases (although they will deny it) mere mortals. Then we have to accept the need to work with inheritance structures that may already in be place and have been designed with less-than-perfect foresight. Assume for example that POLYGON has already been designed and has descendants such as CONVEX_POLYGON and CONCAVE_POLYGON. In other words, the existing inheritance structure does not take into account the difference between ``variable'' and ``fixed'' polygons. Then a new programmer comes in and has a need for RECTANGLE; writing it as an heir to POLYGON seems the obvious solution. Policy 1 precludes doing this without first reorganizing the entire inheritance structure. This is unrealistic in a practical development environment. True, we should accept the need for regular improvements of inheritance structures, reflecting improved understanding of software artefacts. But as anyone who has managed a class library in an industrial environment will appreciate, such evolution should be carefully planned and properly controlled. One cannot accept a situation in which new but legitimate uses of a class require constant changes in the design of existing libraries. Policy 2 is only marginally better. It does allow the programmer to define RECTANGLE as an heir to POLYGON but prevents him from using polymorphism in this case; it is not possible, for example, to define a data structure such as polygon_stack: STACK [POLYGON] and push a RECTANGLE object onto polygon_stack. This is particularly frustrating in an application that never even uses add_vertex. Again, the only solution is to redesign the inheritance structure. Policies 1 and 2 are overly inflexible. In contrast, the idea of policy 3 is to avoid bothering programmers with unnecessary restrictions when their use of inheritance cannot possibly lead to any type failure. The criterion to apply is obvious in the example given: the checker should reject any software system that contains both of the operations + p := r + p.add_vertex (...) Because checkers cannot realistically carry out flow analysis, it is not necessary to check whether or not these two instructions can be executed on the same control flow path; the mere presence of both in the same system is reason enough for the checker to reject that system. In other words, we accept without regret that the following sequence (with n an integer entity) will be rejected even though it cannot possibly lead to a type failure: if n >= 0 then p := r end if n < 0 then p.add_vertex (...) end The reason for this was discussed in [AN EARLIER] section [OF THE PAPER]: checkers should only be bound to perform ``reasonable'' controls. For a checker to recognize that the above is safe requires control flow analysis, whose cost, if it is at all possible, is not justified by the benefit. Using the terminology of [AN EARLIER] section, we are past the point of diminishing returns. As noted [EARLIER], type checking is always a pessimistic strategy; the only question is what degree of pessimism is acceptable. Rejecting extracts of the above form appears acceptable because it is hard to conceive of them being used in useful programs. This is not true, however, of the much stronger pessimism implied by policies 1 and 2, which leads to rejection of programs that, for the reasons discussed above, are useful and even needed. How policy 3 can be implemented at reasonable cost will be discussed [IN LATER SECTIONS]. 4.2 Further reflections on inheritance The argument made above for orthogonal export and inheritance mechanisms was that inheritance structures may be temporarily imperfect. The implied consequence is that eventually these structures will be perfected; then policies 1 and 2 could be implemented if we were willing to implement tougher library management procedures (forcing restructuring when appropriate, and requiring new heir designs to wait in the meantime). Unfortunately, even such an approach may not be viable. In practice we must probably accept that some inheritance structures will always be imperfect. Inheritance is the application to software engineering of the most fundamental of all scientific techniques: classification. The modern forms of the natural sciences, since Linnaeus, and of mathematics, at least since Bourbaki, were borne out of systematic efforts to classify natural and artificial objects; object-oriented programming attempts the same for software objects. Classification is humanity's attempt to bring a semblance of order to the description of an otherwise rather chaotic world. As anyone who has ever tried to use a botany book to recognize actual flowers knows, classifications never quite achieve perfection. Close as one can get to a fully satisfactory system, a few exceptions and special examples will remain. Moving from botany to zoology, the cliche' example of ostriches and flying illustrates these problems well. In class BIRD, it seems appropriate to include and export a feature ``fly''. But a descendant class such as STRUTHIO (the name of the genus that includes ostriches) should not export ``fly''. This is really the Eiffel form of selective inheritance, or rejecting part of your heritage: the rejected feature is still there internally, but not visible by your clients. (The discussion of selective inheritance in section 10.5.3 of ``Object-Oriented Software Construction'' is too restrictive in this respect: it seems to reject any form of selective inheritance, even though Eiffel has always supported the form discussed here.) In a case such as this one there does not seem to be a much better solution. Any other classification of birds, for example into flying and non-flying ones, would miss key criteria for distinguishing between various classes of birds, and would undoubtedly cause bigger problems than the original solution. Multiple inheritance from a class FLYING_THING would not help much. So far, only a minority of Eiffel users have admittedly reported ostrich-oriented programming as their major area of technical interest. But the need to deal with imperfect inheritance structures seems universal. Of course, we should strive to make these structures as complete and regular as possible. But we must also accept that unexpected special cases and exceptions may always occur. When they do and the programmer is only performing safe manipulations, the programming environment should help him, not put undue restrictions in his way. - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- End of extract - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Bertrand Meyer bertrand@eiffel.com
paj@mrcu (Paul Johnson) (01/28/91)
>In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes: >>I disagree with allowing a class to access inherited features that are >>not exported. It does not make sense that secret features are known to >>heir of a class. If a feature is not publicly known, then how can you >>know about the feature to use in the descendent class. > >I like C++'s solution to this, the private/protected/public distinction. I think the idea is that a child should be able to see all of its parents attributes so that the implementation is not constrained by the decisions of the parents' implementors. If someone decides to construct a class which inherits from a single parent simply to export more attributes then that is their buisness. They have designed and built a new class. If the interface is bad, or the invarients no longer hold or some similarly idiotic thing then that is the programmer's fault for producing a bad design. Eiffel is not a bondage and discipline language, and like any good language it will give the programmer plenty of rope. If he hangs himself, well he should have taken more care and heeded the safty warnings. The big advantage of Eiffel over C++ is that thanks to conceptual simplicity, all the dangerous things in Eiffel can be clearly signposted. C++ forces you to take the blade-guards off in order to use it. The private/protected/public system is just sugar and I cannot see how it helps anyone. I can see how it would get in their way though. Paul. -- Paul Johnson UUCP: <world>!mcvax!ukc!gec-mrc!paj --------------------------------!-------------------------|------------------- GEC-Marconi Research is not | Telex: 995016 GECRES G | Tel: +44 245 73331 responsible for my opinions. | Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244
chip@tct.uucp (Chip Salzenberg) (01/28/91)
According to rick@tetrauk.UUCP (Rick Jones): >In article <1991Jan24.214652.18515@Think.COM> barmar@think.com (Barry Margolin) writes: >>I like C++'s solution to this, the private/protected/public distinction. >>The private members of a class are only visible within the class, internal >>to the "black box". Protected members are visible to derived classes ... >>And public members are fully exported. > >The problem I see with this approach is that the designer of a class has to >anticipate every possible way in which his class may be used by another >programmer. I'm sorry, but I don't believe that any method of code reuse -- whether inheritance, delegation or "editor block-copy subroutines" -- will ever be able to guarantee 100% reuse *without modification*. The C++ public/protected/private specifications are a concise way for the programmer of a class to express his expectations as to future derivation from the given class. If a second programmer thinks that she needs to get at a private member of such a class, that is a hint that the base class's author didn't anticipate the second programmer's intended use of the class. It will imply to a good programmer that either (1) the derivation in question is a Bad Idea, or (2) the base class requires some adjustment before the derivation will work. I don't see how eliminating the "private" keyword would fix this basic difficulty with deriving from another programmer's classes. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "If Usenet exists, then what is its mailing address?" -- me "c/o The Daily Planet, Metropolis." -- Jeff Daiell
chip@tct.uucp (Chip Salzenberg) (01/28/91)
According to tynor@hydra.gatech.edu (Steve Tynor): >In practice, I've found C++ `private's to be overly restrictive and in >violation of the rule "it is not the buisiness for a class to decide how >it may be extended in the future" (paraphrased from OOSC). An interesting "rule" -- one with which I happen to disagree. In any case, language features don't stand up and say, "Use me!" Who can say that there will never be good reason to make members private? Not I -- even though I use "protected" five times more often than I use "private". >It's another example of a C++ feature that actually reduces the reusability >of C++ classes (non-virtual member functions being the other obvious one). Virtual functions are available when appropriate; but I'm just as glad that I need not pay the performance penalty when I don't need them. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "If Usenet exists, then what is its mailing address?" -- me "c/o The Daily Planet, Metropolis." -- Jeff Daiell
chrisv@runx.oz.au (Chris Velevitch) (01/30/91)
In article <1991Jan24.214652.18515@Think.COM> barmar@think.com (Barry Margolin) writes: >In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes: >>I disagree with allowing a class to access inherited features that are >>not exported. It does not make sense that secret features are known to >>heir of a class. If a feature is not publicly known, then how can you >>know about the feature to use in the descendent class. > >I like C++'s solution to this, the private/protected/public distinction. >The private members of a class are only visible within the class, internal >to the "black box". Protected members are visible to derived classes; >frequently class derivation is used to extend behavior, in which case it is >reasonable to allow a more intimate relationship between the base class and >the subclasses, and protected members can be used for this. And public >members are fully exported. I can't see the real need for the protected members, because you are publicly announcing the existence of the members. You might as well just make them public. The class specifications remainings the same. I believe that by making features protected, you are making a supposition on how the class will be used. You may find that once having made it known the feature exists, a user will find a case in which they only want to buy the feature and not inherit it. -- Chris Velevitch RUNX Unix Timeshare | Internet: chrisv@runxtsa.runx.oz.au Sydney NSW, Australia. | UUCP: uunet!runxtsa.runx.oz.au!chrisv
jbuck@galileo.berkeley.edu (Joe Buck) (01/31/91)
In article <808@puck.mrcu>, paj@mrcu (Paul Johnson) writes: |> The big advantage of Eiffel over C++ is that thanks to conceptual |> simplicity, all the dangerous things in Eiffel can be clearly |> signposted. C++ forces you to take the blade-guards off in order to |> use it. The private/protected/public system is just sugar and I |> cannot see how it helps anyone. I can see how it would get in their |> way though. Read Grady Booch's book and he'll tell you why. The private/protected/ public feature is one of his favorite things about the language, and he gives good arguments. The problem is that you need to know how to use it. -- Joe Buck jbuck@galileo.berkeley.edu {uunet,ucbvax}!galileo.berkeley.edu!jbuck
bertrand@eiffel.UUCP (Bertrand Meyer) (01/31/91)
Anyone who wants to argue that the Eiffel policy is not the appropriate one is going to have to leave pious generalities and offer serious technical arguments. Will anyone take up the simplest example (discussed in my posting <485@eiffel.UUCP>): a class POLYGON exists and has a procedure ``add_vertex'', which it exports. You want to add a class RECTANGLE. What do you do? 1. Decide not to use inheritance. No comment. 2. Redo the existing inheritance hierarchy. Of course, even assuming you are permitted to do it, this will only save you temporarily: someone else, or you a while later, will want to perform new changes based on criteria other than fixed versus variable number of vertices: for example regular versus irregular, etc. You'll soon have a total mess if you are trying to keep up with various conflicting classifications. 3. Accept that RECTANGLE inherits from POLYGON and does not export ``add_vertex''. (This procedure violates the invariant of class RECTANGLE anyway, so the consistency rules forbid it to be exported, but of course non-exported routines do not have to maintain the invariant.) To avoid burdening the compiler writer, call this ``implementation inheritance'' and disallow polymorphic assignments of the form p := r where p is of type POLYGON and r of type RECTANGLE. 4. Accept that RECTANGLE inherits from POLYGON and does not export ``add_vertex''. Permit polymorphic assignments of the above form; but have the type checker reject as invalid any system which would contain both such an assignment and a call p.add_vertex (...), since it may lead to a run-time inconsistency. Do not introduce any distinction between ``implementation inheritance'' and any other kind. Based on not inconsiderable experience with the design of O-O systems and class hierarchies, I believe that solution 4 is the only reasonable one. I would be interested to see any technical counter-argument, and alternative solutions to the problem mentioned above. Of course solution 4 causes more work for implementers, since type checking must now be done on an entire system rather than a single class. But that's what computers are for: tedious, exhaustive work, especially when (as here) the consequences of not doing that work are potentially rather damaging. In earlier articles I have described a strategy for doing the work in an incremental fashion so that its performance overhead is acceptable. As an aside (meant to be thought- rather than flame-provoking) I have come to realize, although not early enough, that information hiding is a greatly overrated goal. What counts is abstraction. Information hiding is often a convenient syntactical way to help achieve abstraction, but is a means rather than an end, and is not desirable in 100% of cases. It must always be tempered with other criteria. Just because David Parnas and many others after him (including the undersigned) have emphasized the potential benefits of information hiding in well-defined cases does not make incantation a substitute for thinking. (Another of these incantations is ``implementation inheritance''. This does not mean anything. In class POLYGON, why would ``add_vertex'' be implementation and ``access_vertex'' specification?) All too often we forget that design methodology and programming languages are meant to help people produce good software, not to restrict their creative freedom. -- -- Bertrand Meyer bertrand@eiffel.com
rick@tetrauk.UUCP (Rick Jones) (01/31/91)
Given that this thread is starting to look like an Eiffel v C++ debate, there is a significant aspect of Eiffel which has been overlooked - assertions. One of the concepts of Eiffel is that the properties of a class may (and should) be specified in terms of the class invariant and pre- and post-conditions of routines. When using inheritance to write a new class, you can access and/or redefine anything in the parent class, but you cannot override the assertions. (In the current version you can override the pre- and post-conditions, but this is a recognised compiler problem which is going to be rectified.) You certainly can't override the invariant, but you can strengthen it by adding additional invariant properties. The correct way to use Eiffel is to be rigorous with the use of assertions, and run all your tests with all assertion checking on. The assertion checking may then be turned off for a production version once you are sure that the assertions never get violated. This is a sometimes subtle but significantly different approach to the problem of protecting the semantics of classes, and avoids the need to hide the implementation details from descendant classes. You can look at it by saying that the interface of a class is public, the implementation is protected, and the assertions are private (it's not an exact analogy, but it gives the right feel). Of course you have to get your assertions correct, but I don't believe that is any harder than getting the public/private/protected bit right. I have already had several occasions to be glad of Eiffel's assertion system, since it has been able to nip in the bud a number of errors which would otherwise have gone unnoticed and become obscure time-bomb bugs. -- Rick Jones Tetra Ltd. Maidenhead, Was it something important? Maybe not Berks, UK What was it you wanted? Tell me again I forgot rick@tetrauk.uucp -- Bob Dylan
chip@tct.uucp (Chip Salzenberg) (02/01/91)
According to bertrand@eiffel.UUCP (Bertrand Meyer): >A class POLYGON exists and has a procedure >``add_vertex'', which it exports. You want >to add a class RECTANGLE. What do you do? > >1. Decide not to use inheritance. >2. Redo the existing inheritance hierarchy. >3. Accept that RECTANGLE inherits from POLYGON and does not export > ``add_vertex''. ... and disallow polymorphic assignments of the form > p := r where p is of type POLYGON and r of type RECTANGLE. >4. Accept that RECTANGLE inherits from POLYGON and does not export > ``add_vertex''. Permit polymorphic assignments of the above form; > but have the type checker reject as invalid any system which would > contain both such an assignment and a call p.add_vertex (...), since > it may lead to a run-time inconsistency. In C++, the add_vertex() member function would almost certainly be virtual; that is, it would act correctly in cases where a |POLYGON*| actually points to an object of type |RECTANGLE|. (If it were not virtual already, it could be made so by the insertion of the word "virtual" at one place in the code.) Given this language feature, add_vertex becomes a non-problem. The implementor of RECTANGLE simply provides an appropriate definition of the RECTANGLE::add_vertex(): a do-nothing function body that returns a failure indication (if appropriate). Analyzing this use of C++ results in a fifth item for Bertrand's list: 5. Accept that RECTANGLE inherits from POLYGON and exports add_vertex, and require the programmer to provide an implementation of add_vertex appropriate for a RECTANGLE. In the real world, how is this solution deficient? >As an aside (meant to be thought- rather than flame-provoking) >I have come to realize, although not early enough, that >information hiding is a greatly overrated goal. Which reminds me of a nice phrase from Leo Brodie's _Thinking_Forth_, that goes something like this: Some programmers use information hiding as if programs are prone to attack from roving bands of hostile programmers. Of course, Forth isn't exactly my favorite programming environment; I prefer driving with seatbelts (but not a padded cell). :-) -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "I want to mention that my opinions whether real or not are MY opinions." -- the inevitable William "Billy" Steinmetz
barmar@think.com (Barry Margolin) (02/01/91)
In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: >4. Accept that RECTANGLE inherits from POLYGON and does not export >``add_vertex''. Permit polymorphic assignments of the above form; >but have the type checker reject as invalid any system which would >contain both such an assignment and a call p.add_vertex (...), since >it may lead to a run-time inconsistency. Do not introduce >any distinction between ``implementation inheritance'' and any other >kind. > >Based on not inconsiderable experience with the design of O-O systems >and class hierarchies, I believe that solution 4 is the only reasonable >one. I would be interested to see any technical counter-argument, >and alternative solutions to the problem mentioned above. If static type checking is a requirement, I believe you are right. However, in OO systems with dynamic type checking and method dispatch, there is another solution: 5. Require that add_vertex (or, generally, all methods) be a virtual function (in languages that require such a distinction to be made). Accept that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE class with a function that reports an error. Permit polymorphic assignments of the form p=r, and accept the fact that it is possible for p.add_vertex(...) to signal an error at runtime. This is not a realistic solution in a language without a decent exception handling mechanism, but that's an excuse for adding exception handling, not for crippling programs. Furthermore, some languages (e.g. CLOS) even allow the class of an object to change dynamically, so there's another solution: 6. As in 5, use virtual function inheritance of add_vertex. RECTANGLE::add_vertex would change the class of self to POLYGON and then call self.add_vertex (which will invoke the POLYGON::add_method, which might further change the class to PENTAGON). In fact, schemes like this may be the only sensible way to implement a system of this type. Consider a context in which the above classes would be used: a graphical drawing program. The user clicks on an object and then selects an operation from a menu (or vice versa). Solution 6 would be useful if you want to allow users to use the rectangle tool to create an object, but later permit them to edit it in such a way that it is no longer a rectangle. Solution 5 is useful if the UI design specifies that objects created using the rectangle tool must always stay rectangular; when the user tries to add a vertex to the object, an error message would be put up telling them that the operation is not appropriate for that object. The logic necessary to put up this warning must exist in the program somewhere, so why not embed it directly in the methods that need it? With solution 4, the code to select an object could not use polymorphic assignment, because eventually the object will be passed to the add_vertex function; it would probably have to use a discriminated union, and all future uses of that union would have to dispatch on the discriminant (isn't this what OO programming was supposed to relieve us from?). If the menu system supports "greying out" inappropriate menu items, this could be done by having a virtual list_allowed_operations or is_operation_allowed method. Either way, I think it would be very difficult to structure the program in such a way that a static type checker could verify that add_vertex is only called on POLYGONs that aren't specialized to a particular number of vertices, unless is_operation_allowed were made a primitive of the language (actually, it's a good idea to have such a primitive, because it would be easy for user-defined functions like this to get out of sync with the actual code). -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
mario@cs.man.ac.uk (Mario Wolczko) (02/01/91)
In article <488@eiffel.UUCP>, bertrand@eiffel.UUCP (Bertrand Meyer) writes: > > Anyone who wants to argue that the Eiffel policy is not the > appropriate one is going to have to leave pious generalities > and offer serious technical arguments. and then offers some design alternatives... ...but the options he then lists are not exhaustive. Eiffel allows the programmer to control the visibility of any feature to clients via the export clause. The problem is that *all* features are visible to children. If, in the implementation of a class (especially a deferred class), the programmer needs a feature (be it a routine or attribute) for purely private use, and which can be provided in a number of different forms, then it should be possible for the programmer to say, ``Don't assume that this will be present in this form in all versions of this class'', ie that children should not be able to use this feature. That's what the protected mode of C++ is for. If the feature is protected, then the implementor of the class is free to change or remove it, provided that the advertised interfaces (to clients and children) work as advertised! An inability to do this violates what I call "the Principle of Decomposition": at any time the programmer should be free to decompose one thing into more than one thing, without affecting the advertised interface or behaviour. If you mention this in isolation, most people nod in agreement. But no mainstream OO language satisfies it! If we substitute "feature" for "thing", then Eiffel fails because of the described behaviour: the extra features appear in the subclass interface. If we substitute "class" for "thing" (ie decompose one class into two classes, related by inheritance), then *no* mainstream OO language can satisfy the principle, because one cannot localise the interaction between parent and child (self/this/current is always bound in the class of the receiver, which may be a descendant of the child). None of this is new to OOP: it's a problem in any ADT theory/language: Can you implement any ADT and not have the implementation show through? The philosophical point is whether you feel that the parent/child boundary consititutes an interface at which hiding should be possible. I do; I know Dave Ungar doesn't (he told me); I'd be interested to hear what Bertrand Meyer thinks. I think the point is well-argued in Snyder's paper [1], and I was surprised when I came across Eiffel that it did not adopt this policy (especially in view of its "software engineering" stance). I feel sure that Bertrand Meyer was aware of this paper when designing Eiffel, so perhaps he could explain his decision to do otherwise? > All too often we forget that design methodology and programming > languages are meant to help people produce good software, not > to restrict their creative freedom. Quite. I want the freedom to create classes with *exactly* the interfaces (client & child) I want. [1] Alan Snyder, Inheritance and the Development of Encapsulated Software Components, Research Directions in Object-Oriented Programming, MIT Press, 1987, pp147-64 (an earlier version appears in OOPSLA 86). Mario Wolczko ______ Dept. of Computer Science Internet: mario@cs.man.ac.uk /~ ~\ The University uucp: mcsun!ukc!man.cs!mario ( __ ) Manchester M13 9PL JANET: mario@uk.ac.man.cs `-': :`-' U.K. Tel: +44-61-275 6146 (FAX: 6280) ____; ;_____________the mushroom project___________________________________
doug@saturn.ads.com (Doug Morgan) (02/01/91)
In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
Will anyone take up the simplest example (discussed in my
posting <485@eiffel.UUCP>): a class POLYGON exists and has
a procedure ``add_vertex'', which it exports.
You want to add a class RECTANGLE. What do you do?
1. ...
2. ...
3. ...
4. Accept that RECTANGLE inherits from POLYGON and does not export
``add_vertex''. Permit polymorphic assignments of the above form;
but have the type checker reject as invalid any system which would
contain both such an assignment and a call p.add_vertex (...), since
it may lead to a run-time inconsistency. Do not introduce
any distinction between ``implementation inheritance'' and any other
kind.
... I would be interested to see any ... alternative solutions ...
--
-- Bertrand Meyer
bertrand@eiffel.com
4. is a bit strict for my taste. I wouldn't mind it as a option,
however. I like being able to do this (say, in CLOS):
5. Accept that RECTANGLE inherits from POLYGON and *does* export
``add_vertex''. Permit polymorphic assignments of the form p := r
where p is of type POLYGON and r of type RECTANGLE and have a
subsequent call of p.add_vertex (...) convert the current RECTANGLE
implementation (and class) of object p to a POLYGON implementation
(and class).
Of course, for this example I changed the "invariants" for the
RECTANGLE class. They are not unreasonable, just beyond the
compile-time restrictions of Eiffel.
Doug Morgan
doug@ads.com
tynor@hydra.gatech.edu (Steve Tynor) (02/01/91)
>From: barmar@think.com (Barry Margolin) ... >that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >class with a function that reports an error. Permit polymorphic >assignments of the form p=r, and accept the fact that it is possible for >p.add_vertex(...) to signal an error at runtime. This is not a realistic >solution in a language without a decent exception handling mechanism, but >that's an excuse for adding exception handling, not for crippling programs. The problem with this sort of solution is that it defers detection of the error until run-time. It is very difficult to statically examine your code to determine whether your program will fail (you have to do a dataflow analysis which is hard in general). I've run into more than a few latent bugs in large Flavors and C++ programs because of this sort of behavior. [ Before someone jumps on my case telling me about C++ pure virtuals, I know about them - in the case I'm describing, they were not used appropriately by the class designer ]. I'm a big believer in doing as much static code analysis as you (or better yet, the compiler) can - I realize that it is not always feasible (eg. in dynamic systems like CLOS and Flavors). =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= "Language is a virus from outer space" - William S. Burroughs Steve Tynor Georgia Tech Research Institute Artificial Intelligence Branch tynor@prism.gatech.edu
barmar@think.com (Barry Margolin) (02/01/91)
In article <TYNOR.91Jan31145748@hydra.gatech.edu> tynor@hydra.gatech.edu (Steve Tynor) writes: >>From: barmar@think.com (Barry Margolin) >... >>that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >>class with a function that reports an error. >The problem with this sort of solution is that it defers detection of the error >until run-time. It is very difficult to statically examine your code to Isn't that what I said? Here's the first paragraph of the posting you quoted above: >>If static type checking is a requirement, I believe you are right. >>However, in OO systems with dynamic type checking and method dispatch, >>there is another solution: Also, do you have any response to my point that the programmer will have to contort his program so that it is possible to statically determine that it meets all type constraints? Maybe it's my Lisp upbringing, but I believe that programs that make good use of OO are hard to write in such a way that they can be statically type-checked, unless you implement extreme restrictions (such as disallowing assigning derived class instances to ancestor class variables, i.e. the original solution 3). I didn't say it explicitly in my previous post, but I think solution 4 (allow assigning p=r, but have the compiler try to check that there are no code paths that would result in RECTANGLE::add_vertex being called) essentially forces the programmer to code as if solution 3 (disallowing the assignment) were used in many real-world applications. The program must somehow remember that not all POLYGON operations can be performed on p after such an assignment. But POLYGON has no idea which of its methods some derived class might someday disallow, so it must assume any of them may be disallowed; in this case, why bother assigning to p at all? I suppose you could assume that most methods won't be disallowed, and wait until the compiler warns you of a violation. Of course, this whole idea of performing a complete flow analysis assumes that all the information is available to the compiler. If one is deriving from a class library for which only the binary and specification are available, this will be kind of difficult, unless the binary somehow includes a useful representation of all the flow dependencies. -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
tynor@hydra.gatech.edu (Steve Tynor) (02/01/91)
In article <1991Jan31.213146.4306@Think.COM> barmar@think.com (Barry Margolin) writes: >>>that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >>>class with a function that reports an error. >>The problem with this sort of solution is that it defers detection of the error >>until run-time. It is very difficult to statically examine your code to > >Isn't that what I said? Here's the first paragraph of the posting you >quoted above: Quite right, my appologies, etc. My post was a little too knee-jerkish. >Also, do you have any response to my point that the programmer will have to >contort his program so that it is possible to statically determine that it I'd best wait to respond lest sin again... =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= inherit STD_DISCLAIMER; Steve Tynor Georgia Tech Research Institute Artificial Intelligence Branch tynor@prism.gatech.edu
joel@decwrl.dec.com (Joel McCormack) (02/01/91)
>that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >class with a function that reports an error. Permit polymorphic >assignments of the form p=r, and accept the fact that it is possible for >p.add_vertex(...) to signal an error at runtime. This is not a realistic >solution in a language without a decent exception handling mechanism, but >that's an excuse for adding exception handling, not for crippling programs. The problem with this sort of solution is that it defers detection of the error until run-time. It is very difficult to statically examine your code to determine whether your program will fail (you have to do a dataflow analysis which is hard in general). Note that the current Eiffel solution, which is to detect a type error only when given a specific system of modules, has the same problem if dynamic loading of modules is allowed--the error cannot be detected until runtime. You can detect the inconsistency at the time you try to load the module, but this may be hours or days into running the program. The user interface editor is a particularly good example: you'd like to be able to write an extendable editor that can deal with subclassess it has never se en, merely by naming them rather than linking against them. Note that dynamic loading creates other similar problems in trying to defer decisions until a ``system'' of modules is known, because the system of modules is open-ended. You can't do global register allocation, you can't defer decisions about what methods can be inlined, etc. - Joel McCormack (decwrl!joel, joel@decwrl.dec.com)
joel@decwrl.dec.com (Joel McCormack) (02/01/91)
xrn evidently ate one of my paragraphs, and didn't wrap lines automatically. Gotta check my .Xdefaults, I guess... >that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >class with a function that reports an error. Permit polymorphic >assignments of the form p=r, and accept the fact that it is possible for >p.add_vertex(...) to signal an error at runtime. This is not a realistic >solution in a language without a decent exception handling mechanism, but >that's an excuse for adding exception handling, not for crippling programs. The problem with this sort of solution is that it defers detection of the error until run-time. It is very difficult to statically examine your code to determine whether your program will fail (you have to do a dataflow analysis which is hard in general). Note that the current Eiffel solution, which is to detect a type error only when given a specific system of modules, has the same problem if dynamic loading of modules is allowed--the error cannot be detected until runtime. You can detect the inconsistency at the time you try to load the module, but this may be hours or days into running the program. The user interface editor is a particularly good example: you'd like to be able to write an extendable editor that can deal with subclassess it has never seen, merely by naming them rather than linking against them. Note that dynamic loading creates other similar problems in trying to defer decisions until a ``system'' of modules is known, because the system of modules is open-ended. You can't do global register allocation, you can't defer decisions about what methods can be inlined, etc. -- - Joel McCormack (decwrl!joel, joel@decwrl.dec.com)
craig@Neon.Stanford.EDU (Craig D. Chambers) (02/01/91)
In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: > > Will anyone take up the simplest example (discussed in my >posting <485@eiffel.UUCP>): a class POLYGON exists and has >a procedure ``add_vertex'', which it exports. >You want to add a class RECTANGLE. What do you do? > >3. Accept that RECTANGLE inherits from POLYGON and does not export >``add_vertex''. (This procedure violates the invariant of class >RECTANGLE anyway, so the consistency rules forbid it to be exported, >but of course non-exported routines do not have to maintain the >invariant.) To avoid burdening the compiler writer, >call this ``implementation inheritance'' and disallow >polymorphic assignments of the form p := r where p is of type >POLYGON and r of type RECTANGLE. > >4. Accept that RECTANGLE inherits from POLYGON and does not export >``add_vertex''. Permit polymorphic assignments of the above form; >but have the type checker reject as invalid any system which would >contain both such an assignment and a call p.add_vertex (...), since >it may lead to a run-time inconsistency. Do not introduce >any distinction between ``implementation inheritance'' and any other >kind. > >Based on not inconsiderable experience with the design of O-O systems >and class hierarchies, I believe that solution 4 is the only reasonable >one. I would be interested to see any technical counter-argument, >and alternative solutions to the problem mentioned above. > >Of course solution 4 causes more work for implementers, since >type checking must now be done on an entire system rather than a >single class. But that's what computers are for: tedious, exhaustive >work, especially when (as here) the consequences of not doing that >work are potentially rather damaging. In earlier articles I have >described a strategy for doing the work in an incremental fashion >so that its performance overhead is acceptable. This issue has bothered me for a while, but I've not posted about it before. Now with this challenge, I can't resist. Option 3 seems like the only reasonable solution to me. And I'm not arguing from an implementor's point of view; I'm not averse to a complex implementation if it supports a nice programming model and environment. The real problem with option 4 is that a large existing legal program can be invalidated by a single added line. Say you have a large application manipulating various shapes, including rectangles and polygons as above. Now say someone adds a single line to the program, say an assignment "p := r". If in the rest of the application there are no such assignments, but many calls on p.add_vertex, then all these calls are suddenly invalidated by the new assignment. This seems incredibly wrong. The legality of earlier add_vertex calls shouldn't depend on whether some future programmer makes an assignment "p := r"; instead, the new assignment should be marked illegal. This exactly corresponds to option 3: making rectangle inherit code from polygon, but not making rectangle a subtype of polygon. Consider the opposite scenario: In a large application, there are countless assignments of the form "p := r", but no calls of p.add_vertex. The environment could allow this to happen, and make rectangle a legal subtype of polygon, by pretending that the add_vertex feature didn't exist (after all, the application didn't need it). Then some programmer adds a call to p.add_vertex. Again, this last call should be marked in error, since earlier treatment of rectangles as subtypes of polygons has disallowed any future use of add_vertex. This approach is a variant of option 3, with the extension that unused features can be ignored when forcing one class to be a supertype of another. This feature is extra-lingual (part of the environment), but may be nice to have around to support reuse of parts of existing code (e.g. the rectangle class is reusing (inheriting from) part of the polygon class by pretending that certain unneeded operations like add_vertex aren't there). So I conclude, on the grounds of practical programming concerns and usability of the resulting language, that option 3 is far preferable to option 4. Actually, option 6 proposed by Barry Margolin (changing the class of a rectangle to a polygon after an add_vertex) may be a useful alternative in some situations. A more general, cleaner facility for changing the implementation of an object at run-time is called dynamic inheritance in Self. Use of this feature would require distinguishing interface from representation/implementation, with rectangle being a particular representation of polygon but not a distinct type. Then after the add_vertex, the representation of a polygon that was a rectangle changes, but not its type (it's still a polygon). None of these options works nicely in all conceivable situations, but instead imposes certain restrictions on use (limits what the programmer can write). I argue that the restrictions imposed by option 3 (assignments require subtype compatibility) and/or option 6 (some subclasses are not separate types) are more reasonable and likely to be acceptable in practice than those for option 4 (existing programs may be invalidated by new code). However, if the programmer needs to manipulate both rectangles and polygons and assign between them, do add_vertex's on polygons, and treat rectangles as a distinct type (have additional behavior for rectangles), then none of these static solutions works. The remaining solution is to perform dynamic type-checking (or, equivalently, conditional assignment in Eiffel) on calls of p.add_vertex to verify that p doesn't contain a rectangle, or to make a version of add_vertex for rectangles that signals a run-time error. -- Craig Chambers
rick@tetrauk.UUCP (Rick Jones) (02/01/91)
In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: > > Will anyone take up the simplest example (discussed in my >posting <485@eiffel.UUCP>): a class POLYGON exists and has >a procedure ``add_vertex'', which it exports. >You want to add a class RECTANGLE. What do you do? This thread seems to me to be exposing one of the fundamental problems of inheritance, which is the distinction between mutable and imutable objects. Given that inheritance implies both specialisation and extension, then if objects are considered fixed once created there is no problem: a rectangle is clearly a descendant of polygon, since it is a specialised version. Everywhere that a fixed polygon may be used, then a fixed rectangle may be used, so a rectangle is type conformant with a polygon. However, when we allow objects to be modified we are seriously challenging the whole concept of taxonomy. In fact in the shapes example, the type conformance appears to be inverted. In terms of modifying vertices, the polygon is type conformant with a rectangle. Both the CLOS solution of dynamically changing the object's class, and Bertrand Meyer's solution of extended compiler type checking, seem to be ultimately trying to address this problem. Both have their advantages and drawbacks. One problem I see with allowing the class to change is that you might not want this to happen inadvertently. Bertrand's solution covers a more general aspect of what might be called "partial inheritance", but I can see the sort of errors the compiler might generate proving very frustrating for programmers, and lead to all sorts of ugly cross-assignments being used in order to keep the compiler quiet. This sort of code could also well appear pointless on first reading, and confuse novice programmers (this is speculation, since I have not yet had to live with such a compiler). Has anyone tried to address this problem by considering that a class essentially has two interfaces? One - the "read" interface - provides information about the object in its current (abstract) state. The other - the "write" interface - offers facilities to change the object. It seems that if the language supported the ability to define not only the class TYPE of a variable, but also the class USAGE (read or write), then the restrictions would be clearer, both to the compiler and to the programmer. It's a little like the C++ "const" qualifier, but taking the semantic implications a lot further. Eiffel would be well suited to handle this notion, since it already distinguishes between functions/attributes and procedures. The concept of declaring an Eiffel reference as "read-only" would simply prevent it being used to call procedures of the class - only access to attributes and functions would be allowed (good Eiffel design avoids the use of functions which have side effects on the the object's abstract state). This is not a substitute for Bertrand's more general global type-checking, but would make code clearer in representing the current use of the object, and catch errors earlier. I believe it could also simplify the global typing problem, since read-only variables would already be limited as to the range of features available to them, but I haven't had time to consider this in any depth. Have any of the theorists out there done any work along these lines? -- Rick Jones Tetra Ltd. Maidenhead, Was it something important? Maybe not Berks, UK What was it you wanted? Tell me again I forgot rick@tetrauk.uucp -- Bob Dylan
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/01/91)
In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to bertrand@eiffel.UUCP (Bertrand Meyer): >>A class POLYGON exists and has a procedure >>``add_vertex'', which it exports. You want >>to add a class RECTANGLE. What do you do? >> >>1. Decide not to use inheritance. >>2. [...] >> ... >In C++, the add_vertex() member function would almost certainly be >virtual; that is, it would act correctly in cases where a |POLYGON*| >actually points to an object of type |RECTANGLE|. (If it were not >virtual already, it could be made so by the insertion of the word >"virtual" at one place in the code.) Sorry, that was not the difficulty. On the contrary, in Eiffel, Smalltalk, and many other OOPL's _all_ procedures are virtual in the Simula/C++ sense. >Given this language feature, add_vertex becomes a non-problem. The >implementor of RECTANGLE simply provides an appropriate definition of >the RECTANGLE::add_vertex(): a do-nothing function body that returns a >failure indication (if appropriate). This is only possible if either: 1. 'add_vertex' was originally declared with a return value or parameter that indicates success or failure. One would not like to add such parameters to the majority of procedures, because they tend to complicate the code too much. 2. The language has an exception mechanism; incidentally, Eiffel has it but C++ hasn't - well, there is a suggestion in the Ellis & Stroustrup book. >Analyzing this use of C++ results in a fifth item for Bertrand's list: > >5. Accept that RECTANGLE inherits from POLYGON and exports add_vertex, > and require the programmer to provide an implementation of add_vertex > appropriate for a RECTANGLE. > >In the real world, how is this solution deficient? The defect of this solution is that _all_ inapproriate invocations of 'add_vertex' are detected only at run time. Some other solutions make it possible to classify the invocations into three classes at compile time: 1. Those that are certainly correct. 2. Those that are certainly wrong and cause an error message. 3. Those that may be right or wrong and need a run-time check. The bad news is that one must limit flexibility and polymorphism if one wants to avoid run-time checks completely. (I'll return to this in another posting.) Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/01/91)
In article <TYNOR.91Jan31145748@hydra.gatech.edu> tynor@hydra.gatech.edu (Steve Tynor) writes: >>From: barmar@think.com (Barry Margolin) >... >>that RECTANGLE inherits from POLYGON. Override add_vertex in the RECTANGLE >>class with a function that reports an error. Permit polymorphic >>assignments of the form p=r, and accept the fact that it is possible for >>p.add_vertex(...) to signal an error at runtime. This is not a realistic >>solution in a language without a decent exception handling mechanism, but >>that's an excuse for adding exception handling, not for crippling programs. > >The problem with this sort of solution is that it defers detection of the error >until run-time. It is very difficult to statically examine your code to >determine whether your program will fail (you have to do a dataflow analysis >which is hard in general). [...] > ... Indeed, not only "very difficult", but in many cases even theoretically impossible. >I'm a big believer in doing as much static code analysis as you (or better yet, >the compiler) can - I realize that it is not always feasible (eg. in dynamic >systems like CLOS and Flavors). I sympathise, but even in languages that are essentially statically typed there are good reasons for some facilities that require run-time checks. The OOPSLA/ECOOP'90 proceedings contains at least two papers that tackle these issues: Lehrmann Madsen & Magnusson & Moller-Pedersen, Palsberg & Schwartzbach. Someone may note that I am here defending run-time checks, while in the immediately preceding posting I was rather on the side of static checking. To adapt a famous Einstein quote: Things should be checked at compile time as much as possible, but no more. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
jacob@gore.com (Jacob Gore) (02/02/91)
/ comp.lang.eiffel / bertrand@eiffel.UUCP (Bertrand Meyer) / Jan 30, 1991 / > Will anyone take up the simplest example (discussed in my > posting <485@eiffel.UUCP>): a class POLYGON exists and has > a procedure ``add_vertex'', which it exports. > You want to add a class RECTANGLE. What do you do? As long as we combine the subtyping (specification) hierarchy with implementation reuse hierarchy (I'm not saying it's a bad thing, although I'm beginning to seriously wonder), the option most obvious to me is: 5. RECTANGLE needs to replace the 'add_vertex' it inherits with one that does nothing. I don't see why RECTANGLE needs to welch on the contract of its superclass in the simplest case, and I don't think it should. However, if POLYGON's 'add_vertex' contains in its contract the postcondition "num_vertices = num_vertices + 1", there is no way RECTANGLE can uphold the contract. So, I see Bertrand's reasoning behind his option 4, but I am wondering if there is a fundamentally different approach to the whole thing... I'd also like to refer people on a rather good letter on this subject, one that demonstrates what its author sees as a fundamental problem (though without offering a concrete solution): Paul W. Abrahams. "Subject: Objectivism" (in ACM Forum). Communications of the ACM, Vol. 34, No. 1, Jan. 1991, pp. 15-16. Jacob -- Jacob Gore Jacob@Gore.Com boulder
chip@tct.uucp (Chip Salzenberg) (02/02/91)
According to rick@tetrauk.UUCP (Rick Jones): >Given that this thread is starting to look like an Eiffel v C++ debate, there >is a significant aspect of Eiffel which has been overlooked - assertions. Please don't cloud discussions with irrelevant issues. Inheritance and information hiding issues are orthagonal to assertion issues. A given language may support the former, the latter, both, or neither. Let's keep the discussions clear. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "I want to mention that my opinions whether real or not are MY opinions." -- the inevitable William "Billy" Steinmetz
bertrand@eiffel.UUCP (Bertrand Meyer) (02/02/91)
From <27A86309.281A@tct.uucp> by chip@tct.uucp (Chip Salzenberg): > >A class POLYGON exists and has a procedure > >``add_vertex'', which it exports. You want > >to add a class RECTANGLE. What do you do? > > In C++, the add_vertex() member function would almost certainly be > virtual; that is, it would act correctly in cases where a |POLYGON*| > actually points to an object of type |RECTANGLE|. (If it were not > virtual already, it could be made so by the insertion of the word > "virtual" at one place in the code.) [As an aside to clarify any misunderstanding: in Eiffel all routines are by default dynamically bound (``virtual''). Applying static binding when appropriate is the compiler's job, not the programmer's.] > Given this language feature, add_vertex becomes a non-problem. The > implementor of RECTANGLE simply provides an appropriate definition of > the RECTANGLE::add_vertex(): a do-nothing function body that returns a > failure indication (if appropriate). A non-problem? For a routine which is supposed to add a vertex, to do nothing at all is a non-problem? The postcondition for add_vertex says ensure number_of_vertices = old number_of_vertices + 1 This is the contract that any implementation must satisfy - or else (more on the ``or else'' below). Doing nothing is a gross violation of this contract. Of course Mr. Salzenberg's solution is not just to ``do nothing'' but to return a ``failure indication''. Returning a failure indication is not that trivial (especially if we are talking about a function that already has a result), but it can be done (if only by adding a global variable), so let's accept it. But then we have changed the contract! The postcondition is now ensure failure_indication or else (number_of_vertices = old number_of_vertices + 1) which means that the specification of the ORIGINAL ``add_vertex'' routine has now changed! The postcondition has become weaker, which is the reverse of what is required if existing clients are to be kept correct. As a result, every piece of code ever written that included a call to the old ``add_vertex'' must be updated - just because someone added the notion or rectangle somewhere. Is that object-oriented design? Is that the best one can do in C++? To be sure, a more acceptable of Mr. Salzenberg's scheme is possible. Instead of a ``do-nothing function body that returns a failure indication'', one may implement ``add_vertex'' in RECTANGLE so that it raises an exception - if the language supports exceptions, of course. This can readily be done in Eiffel; in fact, the theoretical and practical role of an exception is precisely to signal that a routine is unable to fulfil its contract. This could have been solution number 5 in the original list. With this approach, there is no need to change existing client software. But the effect is essentially to renounce static type checking for run-time checks, an unpleasant perspective. All this confirms that, as Rick Jones recently commented here, one cannot pursue this discussion properly without some reference to the specification of software elements. Assertions serve that purpose. Without assertions, you lose any perspective of the reason why a certain piece of code does what it does, and for problems such as the one discussed here there is the ever present risk of falling into mere hacking. -- Bertrand Meyer bertrand@eiffel.com
brucec@phoebus.labs.tek.com (Bruce Cohen;;50-662;LP=A;) (02/02/91)
In article <1991Jan31.185534.24530@Think.COM> barmar@think.com (Barry Margolin) writes: > ... > Furthermore, some languages (e.g. CLOS) even allow the class of an object > to change dynamically, so there's another solution: > > 6. As in 5, use virtual function inheritance of add_vertex. > RECTANGLE::add_vertex would change the class of self to POLYGON and then > call self.add_vertex (which will invoke the POLYGON::add_method, which > might further change the class to PENTAGON). > > In fact, schemes like this may be the only sensible way to implement a > system of this type. Consider a context in which the above classes would > be used: a graphical drawing program. The user clicks on an object and > then selects an operation from a menu (or vice versa). Solution 6 would be > useful if you want to allow users to use the rectangle tool to create an > object, but later permit them to edit it in such a way that it is no longer > a rectangle. There was a lengthy and somewhat inconclusive discussion in this newsgroup a few months ago of just this scheme in relation to using Self for a graphic system. I started it by noting that Self allows this technique in a graphic system (I hadn't had any real experience with CLOS at the time, especially the MetaObject Protocol). As mentioned then, there are additional benefits to changing the inheritance of an object dynamically: tailoring the space and/or time complexity of operations on the object to match its current needs (e.g., rectangles which are orthogonal to the coordinate axis can be drawn faster than other polygons on many systems). Structure objects (graphic container objects with semantics about how they display their contained graphics) can change their semantics easily. This is handy for modifying the rendering of contained objects in job lots. Most of the benefits are a result of the size and dynamic complexity of graphics, especially 3D photorealistic graphics. It's not at all uncommon to have a system containing hundreds of thousands or millions of primitive graphic objects, arranged in many complex structures. Space and time savings at rendering time become very important in these systems; it's usually a good tradeoff to lose some time in dynamically modifying the objects in an editing phase. -- ------------------------------------------------------------------------ Speaker-to-managers, aka Bruce Cohen, Computer Research Lab email: brucec@tekchips.labs.tek.com Tektronix Laboratories, Tektronix, Inc. phone: (503)627-5241 M/S 50-662, P.O. Box 500, Beaverton, OR 97077
bertrand@eiffel.UUCP (Bertrand Meyer) (02/02/91)
From <1991Feb1.015749.10111@Neon.Stanford.EDU> by craig@Neon.Stanford.EDU (Craig D. Chambers): > > > >a class POLYGON exists and has > >a procedure ``add_vertex'', which it exports. > >You want to add a class RECTANGLE. What do you do? > > > >3. Accept that RECTANGLE inherits from POLYGON and does not export > >``add_vertex''. [...] To avoid burdening the compiler writer, > >call this ``implementation inheritance'' and disallow > >polymorphic assignments of the form p := r where p is of type > >POLYGON and r of type RECTANGLE. > > > >4. Accept that RECTANGLE inherits from POLYGON and does not export > >``add_vertex''. Permit polymorphic assignments of the above form; > >but have the type checker reject as invalid any system which would > >contain both such an assignment and a call p.add_vertex (...), since > >it may lead to a run-time inconsistency. Do not introduce > >any distinction between ``implementation inheritance'' and any other > >kind. > > Option 3 seems like the only reasonable solution to me. And I'm not > ... > The real problem with option 4 is that a large existing > legal program can be invalidated by a single added line. > > Say you have a large application manipulating various shapes, > including rectangles and polygons as above. Now say someone adds a > single line to the program, say an assignment "p := r". If in the > rest of the application there are no such assignments, but many calls > on p.add_vertex, then all these calls are suddenly invalidated by the > new assignment. This seems incredibly wrong. The legality of earlier > add_vertex calls shouldn't depend on whether some future programmer > makes an assignment "p := r"; instead, the new assignment should be > marked illegal. There is a point here. But you may look at it in the following way. What is illegal is the presence in the same system of both of the instructions [A] p := r [B] p.add_vertex The error may be in one or in the other. In the situation described by Mr. Chambers, assume we have an incremental compiler, and a lot of existing code uses [B]. If someone tries to add [A] to the system, then this will be flagged by the incremental compiler, which will point at the most obvious source of the problem: the new occurrence of [A], not the existing occurrences of [B]. This invalidates the addition, not the existing code, which is what Mr. Chambers wants. In this case we get the effect of solution 3, but with much less burden on the programmer because many cases which solution 3 would flag as invalid will be accepted since they don't raise any problem. My impression is that uniformly rejecting the cases which satisfy 4 but not 3 would significantly restrict the programmer's power of expression, and that they are important and useful in practice. -- -- Bertrand Meyer bertrand@eiffel.com
amanda@iesd.auc.dk (Per Abrahamsen) (02/03/91)
Sorry to interrupt, but why does nobody accept Bertrand Meyers first
two solutions?
>>>>> On 1 Feb 91 16:30:48 GMT, jacob@gore.com (Jacob Gore) said:
Jacob> However, if POLYGON's 'add_vertex' contains in its contract the
Jacob> postcondition "num_vertices = num_vertices + 1", there is no
Jacob> way RECTANGLE can uphold the contract.
But in that case a RECTANGLE is not, conceptually, a POLYGON. There
are then two possibilities (as Bertrand Meyer mentioned in
<488@eiffel.UUCP>:
1) The postcondition in correct: You can always add a vertex to a
polygon.
In this case, a rectangle is not a polygon, and it is misleading to
use inheritance.
2) The postcondition is wrong. You can only add a vertex to some
kinds of polygons.
If the postcondition is wrong, it should be fixed, like any other
bug!
In a program which should be read and understood by other people
later, these are the only long term solutions. However, when you are
creating a throw away prototype, inheritance might be useful
nonetheless. In this case, it does not matter much whether the
language provides "implementation inheritance (solution 3), "extended
type check" (solution 4), or "run time check" (solution 5), except
that (5) is the most flexible, and (3) the most safe.
craig@Neon.Stanford.EDU (Craig D. Chambers) (02/04/91)
In article <1081@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes: >Has anyone tried to address this problem by considering that a class >essentially has two interfaces? One - the "read" interface - provides >information about the object in its current (abstract) state. The other - the >"write" interface - offers facilities to change the object. It seems that if >the language supported the ability to define not only the class TYPE of a >variable, but also the class USAGE (read or write), then the restrictions would >be clearer, both to the compiler and to the programmer. It's a little like the >C++ "const" qualifier, but taking the semantic implications a lot further. I've noticed the same read-only v. read-write interface problem. It frequently arises in parameterized data types like collections, in which the read-only interfaces to two differently instantiated collections are subtype-related in the same way that the instantiating types are related (read-only-collection[S] <= read-only-collection[T] iff S <= T, where "<=" means "is a subtype of"), the two write-only interfaces are related in the opposite way, and consequently the two read-write interfaces are unrelated. I described this situation in a posting to comp.object a while back. I've been working on a new o-o language design and have been thinking about ways to allow the programmer to specify "sub-interface" indications for each member, much in the same way that public v. private subinterfaces are distinguished in many languages. Then uses of the type (e.g. type declarations) could also specify a subinterface, such as the "read-only" subinterface of a collection. The present polygon/rectangle example could benefit from subinterfaces, since the add_vertex operation could be qualified with the "resizable" subinterface. I'm concerned a bit about achieving the desired syntactic conciseness; "read-only array" is a lot longer than "array". Maybe the compiler could somehow infer the right subinterface. Of course, subinterfaces aren't strictly required, since each subinterface could just be a separate type/class. However, subinterfaces would work well with hierarchies of e.g. collections each with similar subinterfaces, while the manual method would quickly get complicated and hard to maintain. So subinterfaces seem like a qualitative increase in expressive power. If they could be integrated with the already-present public vs. private subinterfaces, then no new language concepts would be needed (just an existing concept generalized). >Eiffel would be well suited to handle this notion, since it already >distinguishes between functions/attributes and procedures. The concept of >declaring an Eiffel reference as "read-only" would simply prevent it being used >to call procedures of the class - only access to attributes and functions would >be allowed (good Eiffel design avoids the use of functions which have side >effects on the the object's abstract state). This sounds like you're proposing a built-in read-only subinterface much like const in C++. I would like the various subinterfaces to be completely user-defined, without any notion of a read-only subinterface embedded in the language. Also, in some cases a function-only subinterface as you suggest could still have the same subtype problems; the question is whether the types of the arguments to the function are supertypes of the argument types of overridden function in the superclass (i.e. the contravariance property of function subtyping). >This is not a substitute for >Bertrand's more general global type-checking, but would make code clearer in >representing the current use of the object, and catch errors earlier. Bertrand's type-checking rules are *less general* than subinterfaces, since what his rule really accomplishes is removing features that are *never* called from the interfaces of classes (and treating inheritance links as subtype links only if assignments of the subclass to the superclass are actually made; this half of the rule doesn't come into play here). With subinterfaces, however, different parts of a class can be ignored in some cases and used in others. For example, the add_vertex feature of polygons could be in a special subinterface marked "resizable", and rectangles could be declared as a subtype of the non-resizable subinterface of polygons. Then "p1 := r" assignments and "p2.add_vertex" calls could appear in the same program, as long as p1 was declared to be the non-resizable subinterface of polygons, and p2 was declared as the resizable interface. This is not possible with Bertrand's type-checking rules. -- Craig Chambers
paj@mrcu (Paul Johnson) (02/04/91)
In article <1081@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes: >In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: >> >> Will anyone take up the simplest example (discussed in my >>posting <485@eiffel.UUCP>): a class POLYGON exists and has >>a procedure ``add_vertex'', which it exports. >>You want to add a class RECTANGLE. What do you do? > > [... Much good stuff deleted ...] > >Has anyone tried to address this problem by considering that a class >essentially has two interfaces? One - the "read" interface - >provides information about the object in its current (abstract) >state. The other - the "write" interface - offers facilities to >change the object. It seems that if the language supported the >ability to define not only the class TYPE of a variable, but also the >class USAGE (read or write), then the restrictions would be clearer, >both to the compiler and to the programmer. It's a little like the >C++ "const" qualifier, but taking the semantic implications a lot >further. > >Eiffel would be well suited to handle this notion, since it already >distinguishes between functions/attributes and procedures. The >concept of declaring an Eiffel reference as "read-only" would simply >prevent it being used to call procedures of the class - only access >to attributes and functions would be allowed (good Eiffel design >avoids the use of functions which have side effects on the the >object's abstract state). This is not a substitute for Bertrand's >more general global type-checking, but would make code clearer in >representing the current use of the object, and catch errors earlier. > >I believe it could also simplify the global typing problem, since >read-only variables would already be limited as to the range of >features available to them, but I haven't had time to consider this >in any depth. Have any of the theorists out there done any work >along these lines? Yes. I have been thinking along precisely these lines and have come up with some ideas which I call "Fine Grain Inheritance". Below is a short summary. I have a couple of papers submitted to `Software -- Practice and Experience' and ECOOP '91. (No offence to the TOOLS '91 people, just a matter of timing). I believe you should split your class up into lots and lots of little superclasses, each implementing one elementary concept (such as "first item" or "read/write current item") and usually having only one or two attributes. Then go through your class heirarchy and split all classes up into "read-only" and "write-only" varients. Then implement them. Don't be frightened of the large amounts of repeated inheritance you wind up with, its OK. Most of your classes will be deferred and you should make all attributes into deferred functions as well, hence allowing them to be either functions or variables in descendant classes. Hence in Dr. Meyer's example, POLYGON will have (amongst others) an ancestor LIST[ VERTEX ], which will have ancestors LIST_READ[ VERTEX ] and LIST_WRITE[ VERTEX ]. RECTANGLE will have (amongst others) an ancestor LIST_READ[ VERTEX_READ ]. Hence not only is it impossible to add or remove verteces of RECTANGLE, it is also impossible to move a vertex (short of reverse assignment). On the other hand a function which needs to enumerate the verteces of a shape will be able to handle both POLYGON and RECTANGLE by taking an argument of type LIST_READ[ VERTEX_READ ]. This scheme becomes completely flexible when a new type rule (which I call the "Sibling-Supertype" rule) is added which (put briefly) allows the following. If the supertypes of A are a subset of the supertypes of B, then A is a supertype of B. These ideas allow the development of totally flexible class libraries. The only problem comes when coarse grain libraries must be reused as well. Of course, tools like "flat" and "short" become absolutely vital in fine grain libraries, as does multiple inheritance. See you all at TOOLS '91. Paul. -- Paul Johnson UUCP: <world>!mcvax!ukc!gec-mrc!paj --------------------------------!-------------------------|------------------- GEC-Marconi Research is not | Telex: 995016 GECRES G | Tel: +44 245 73331 responsible for my opinions. | Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244
chip@tct.uucp (Chip Salzenberg) (02/05/91)
According to sakkinen@jytko.jyu.fi (Markku Sakkinen): >In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>RECTANGLE::add_vertex(): a do-nothing function body that returns a >>failure indication (if appropriate). > >This is only possible if either: >1. 'add_vertex' was originally declared with a return value or parameter > that indicates success or failure. >2. The language has an exception mechanism ... I discern that my suggestion was inadequate. I withdraw it. I now agree, however, with those who suggest that RECTANGLE is not an appropriate derivation of POLYGON. There is an assumption built into a POLYGON interface that includes an add_vertex() function: that a POLYGON may be modified in its number of vertices. The derivation of RECTANGLE from POLYGON violates that assumption. Separate inheritance subtrees of POLYGON, such as FIXED_POLYGON and VARIABLE_POLYGON, now seem to me the best way to go. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "Most of my code is written by myself. That is why so little gets done." -- Herman "HLLs will never fly" Rubin
pfkeb@ebnextk.SLAC.Stanford.EDU (Paul Kunz) (02/05/91)
In article <27ADD06B.46A3@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
I now agree, however, with those who suggest that RECTANGLE is not an
appropriate derivation of POLYGON.
There is an assumption built into a POLYGON interface that includes an
add_vertex() function: that a POLYGON may be modified in its number of
vertices. The derivation of RECTANGLE from POLYGON violates that
assumption. Separate inheritance subtrees of POLYGON, such as
FIXED_POLYGON and VARIABLE_POLYGON, now seem to me the best way to go.
--
Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip>
"Most of my code is written by myself. That is why so little gets done."
-- Herman "HLLs will never fly" Rubin
I've been following this thread a few days but have not had time to
respond but have come to the same conclusion. If POLYGON is going to
be SuperClass of RECTANGLE (and TRIANGLE and SQUARE) then it can not
possibly have a add_vertex() function. This is the error. Only
a very specialized class of VARIABLE_POLYGON which inherits from POLYGON
can have such a method.
In my mind the confusion comes from the fact that a RECTANGLE (or
TRIANGLE or SQUARE) is something real that we can see, feel, and touch.
They all inherit from something generic, not real, not touchable. On the
other hand, a n-point POLYGON is also real that can be changed to
n+1 points. It inherits from something generic as well. The mistake
is to call this generic thingy a POLYGON and put a add_vertex function
in it.
That's my two cents.
caseau@maya.bellcore.com (Yves J Caseau) (02/05/91)
Here is my $0.02 contribution to the problem: ============================================= 1. This is a well-known and common problem. In the type theory and object-oriented database community, the example is a class point with an instance variable x, and the subclass is the set of point for which x is between 0 and 10. So if we change the instance variable, the "class" to which the object belongs must change, or an error must be detected. The question is: given a class A, and a subclass B which has a characteristic property, how to represent it into an object-oriented system? Obviously it depends if your system supports such characteristic properties. If it does (Eiffel with invariants, Semantic networks with specialisation predicates (eg CLASSIC), LORE with selection sets, FOL-based object-oriented systems) you have a "nice" solution: inheritance with automatic error detection (Eiffel: the invariant is violated) or reclassification. If the system does not support such properties, you have to implement them yourself, copying one of the two previous solutions. This means that each method that returns an object from B must be specialized (if it is inherited) so as to check the characteristic property (and decide between error or re-classification, according to the language features (C++ vs. CLOS) 2. My solution to the POLYGON/RECTANGLE problem. I tend to be simple minded: a. a RECTANGLE is a POLYGON (thefore there must be inheritance) b. Adding a vertex to a polygon produces ANOTHER polygon (a different mathematical object) c. Thus, rectangle inherit the property add_vertex, which produces a pentagone from a rectangle Don't flame me on this, I know this is cheating ... but it makes a lot of sense.
rmartin@clear.com (Bob Martin) (02/05/91)
In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to bertrand@eiffel.UUCP (Bertrand Meyer): >>A class POLYGON exists and has a procedure >>``add_vertex'', which it exports. You want >>to add a class RECTANGLE. What do you do? >> >>1. Decide not to use inheritance. >>2. Redo the existing inheritance hierarchy. >>3. Accept that RECTANGLE inherits from POLYGON and does not export >> ``add_vertex''. ... and disallow polymorphic assignments of the form >> p := r where p is of type POLYGON and r of type RECTANGLE. >>4. Accept that RECTANGLE inherits from POLYGON and does not export >> ``add_vertex''. Permit polymorphic assignments of the above form; >> but have the type checker reject as invalid any system which would >> contain both such an assignment and a call p.add_vertex (...), since >> it may lead to a run-time inconsistency. > >In C++, the add_vertex() member function would almost certainly be >virtual; [...] add_vertex becomes a non-problem. The >implementor of RECTANGLE simply provides an appropriate definition of >the RECTANGLE::add_vertex(): a do-nothing function body that returns a >failure indication (if appropriate). > >In the real world, how is this solution deficient? Assume that this example were part of a very large system which added points to POLYGONs all over the place. Assume also that there had never before been a reason for add_point to fail. Your proposed solution _changes_ the interface to class POLYGON since now some POLYGONs (or their derivatives i.e. RECTANGLEs) will issue failure for add_point. Thus it breaks old working code. > >>As an aside (meant to be thought- rather than flame-provoking) >>I have come to realize, although not early enough, that >>information hiding is a greatly overrated goal. This is not a flame, it is a provoked thought. Some people may overrate a goal. That does not devalue the goal. > -- +-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for | | rmartin@clear.com |:R::R:C::::M:M:M:M:| my words but me. I want | | uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all | +----------------------+:R::R::CCC:M:::::M:| the blame. So there. |
adam@visix.com (Adam Kao) (02/06/91)
I have some random thoughts on this topic; Yves J Caseau's posting brought my ideas together, to the point where I would like to pursue them further in public. I have always felt uncomfortable with the emphasis on "Class" in languages like Smalltalk, C++ etc. To me, the notion of an Object seems much more basic and fundamental than a Class. Everybody knows that real-world classes are not well-behaved; they are not disjoint, they are not proper subsets, class membership changes over time. I see object oriented techniques such as tree heirarchies, multiple inheritance, etc., as attempts to make Classes well-behaved. But in doing so, I feel that we increasingly distance our notion of Class from the common-sense notion of class, which necessarily makes object-oriented programming harder to understand. In the real world, objects do not, strictly speaking, belong to a class. A classification system is imposed on real objects by our world view; class membership is an answer to a question we phrase. Different question, different answer. To my mind, classes can be viewed as schema that define invariants among their members. Every object is then potentially a member of as many classes as we care to define. Since any trait of an object can be considered a defining invariant for some class, it then becomes inevitable that any trait change will remove the object from some classes and add it to others. An abstract RECTANGLE satisfies all the invariants of a POLYGON, so it is natural to have RECTANGLE inherit from POLYGON. But a RECTANGLE has additional invariants, so operations that are closed over POLYGONs are not closed over RECTANGLEs. We know how to add vertices to POLYGONs. I think it ridiculous to say we do not know how to add vertices to RECTANGLEs. The problem is, adding a vertex to a RECTANGLE gives us something that is not a RECTANGLE. I do not see this as a problem. Why do we expect that all operations on an Object must be closed over its Class? Most languages have type-casting and type-promotion. In C, we can add a double to an int, and the result will be a double. Is this kind of arithmetic forbidden in object-oriented programming? To me, the statements "p := r" and "p.add_vertex(...)" are perfectly compatible. The "p := r" should imply a cast; if we want to make the assignment, it means we want to treat rectangles as polygons. Surely the explicit conversion "p := r.make_polygon()" followed by "p.add_vertex(...)" is acceptable; why not accept even "r.add_vertex(...)" as a shorthand? I believe that this discussion is evidence that our notion of a Class is too weak to support all the uses we expect. Of course, I was predisposed to this conclusion. But, I most certainly hope to hear more discussion on any side of this issue. Adam
blerner@empire.cs.umass.edu (Barbara Lerner) (02/06/91)
> From: adam@visix.com (Adam Kao) > [..] > An abstract RECTANGLE satisfies all the invariants of a POLYGON, so it > is natural to have RECTANGLE inherit from POLYGON. But a RECTANGLE > has additional invariants, so operations that are closed over POLYGONs > are not closed over RECTANGLEs. > > We know how to add vertices to POLYGONs. I think it ridiculous to say > we do not know how to add vertices to RECTANGLEs. The problem is, > adding a vertex to a RECTANGLE gives us something that is not a > RECTANGLE. > > I do not see this as a problem. Why do we expect that all operations > on an Object must be closed over its Class? > > Most languages have type-casting and type-promotion. In C, we can add > a double to an int, and the result will be a double. Is this kind of > arithmetic forbidden in object-oriented programming? > > To me, the statements "p := r" and "p.add_vertex(...)" are perfectly > compatible. The "p := r" should imply a cast; if we want to make the > assignment, it means we want to treat rectangles as polygons. Surely > the explicit conversion "p := r.make_polygon()" followed by > "p.add_vertex(...)" is acceptable; why not accept even > "r.add_vertex(...)" as a shorthand? [..] The distinction between adding a vertex to a polygon and adding a vertex to a rectangle is the following. When a vertex is added to a polygon, no new object is created. The same polygon object exists but has one more vertex. If we take the point of view that we can add a vertex to a rectangle with the result begin a pentagon, we now have two choices: 1. Create a new Pentagon object that is a "copy" of the rectangle with the added vertex. 2. Add the vertex to the rectangle object, and change its class from rectangle to pentagon. The first solution is fundamentally different from adding a vertex to the object. For example, if we have an object O with a field p of type polygon. O.p := make_polygon(..) O.p.add_vertex(..); In this case, O.p will be a polygon with one more vertex than it was originally created with. Suppose instead, we do: O.p := make_rectangle(..) O.p.add_vertex(..); Now, O.p is still a rectangle since we haven't changed O.p, but created a new object instead. Of course, we could say: O.p := O.p.add_vertex(..); This places the burden on the user of add_vertex to make sure that all references to the rectangle are appropriately updated. Keep in mind that there may be more than one object referenceing the rectangle. The second solution is not type safe in the following situation. Suppose we have an object O with two fields: p of type polygon, and r of type rectangle. Now suppose we execute the following code with add_vertex changing the class of the rectangle to pentagon. O.r := make_rectangle(..); O.p := O.r; O.p.add_vertex(..); If we change the class of the rectangle to pentagon, then O.r now refers to a pentagon while the class of O.r is declared to be rectangle. *Accurately* detecting this situation at compile-time is probably impossible. Detecting it at runtime would require every object to maintain backpointers to all objects that reference it, so that all class changes could be type-checked. Barbara Lerner -- Barbara Staudt Lerner Deparment of Computer and Information Science Lederle Graduate Research Center University of Massachusetts Amherst, MA 01003 blerner@cs.umass.edu
bertrand@eiffel.UUCP (Bertrand Meyer) (02/07/91)
From <1991Feb5.130359.9735@bellcore.bellcore.com> by caseau@maya.bellcore.com: > 1. This is a well-known and common problem. In the type theory and object-oriented > database community, the example is a class point with an instance variable x, > and the subclass is the set of point for which x is between 0 and 10. So if we > change the instance variable, the "class" to which the object belongs must > change, or an error must be detected. > It may be a well-known and common problem but I don't remember reading anything really enlightening in the literature about the fields mentioned by Yves Caseau. Here is a way to model Mr. Caseau's example. There is a class A class A export x, set_x, ... feature x: INTEGER; set_x (new_value: INTEGER) is do x := new_value end invariant -- Nothing relevant end and an heir B: class B export x, ... feature ... invariant 0 <= x; x <= 10 end B is fine since its invariant is stronger than the invariant of the parent class A. There is a a problem with procedure set_x, however: the basic rule of class correctness states (in particular) that every exported routine should preserve the invariant. (See chapter 7 of OOSC and the article ``Programming as Contracting''.) Since set_x does not necessarily preserve the invariant, ergo - it cannot be exported in B. So we are back to the situation discussed in previous postings, and the need to allow a class to hide features exported by its parents. It is not possible to redefine set_x in B to be more demanding, as in class B export x, ... feature ... set_x (new_value: INTEGER) is require 0 <= new_value; new_value <= 10 do x := new_value end invariant 0 <= x; x <= 10 end This does not work, since the new precondition would be stronger (again, see above references). It would make it impossible for a client to guarantee that a call will be correct through a scheme of the form [X] b1: B; ... if some_consistency_check (my_value) then b1.set_x (my_value) else ... Other action ... end There is a conceptual trick, however, that often works. Make the precondition abstract by introducing an exported function acceptable_value in A: acceptable_value (n: INTEGER): BOOLEAN is do Result := true end and redefine it in B (keeping it exported): acceptable_value (n: INTEGER): BOOLEAN is do Results := (0 <= x) and (x <= 10) end Then you can indeed export set_x in B because clients can use scheme [X] safely under the following variant: [Y] if acceptable_value (my_value) then b1.set_x (my_value) else ... Other action ... end The precondition of set_x does not change in B. (Of course the concrete precondition changes since B redefines function acceptable_value; and it may even appear to have been strengthened, violating the contracting rules as before. But this is in fact not true. What counts is the abstract precondition, as seen from the oustside, by clients; and that one has remained the same, as expressed by the property lambda n . acceptable_value (n).) This technique is used quite frequently in the Basic Eiffel Library. -- Bertrand Meyer bertrand@eiffel.com
bertrand@eiffel.UUCP (Bertrand Meyer) (02/07/91)
From <1991Feb5.130359.9735@bellcore.bellcore.com> by caseau@maya.bellcore.com (Yves J Caseau): > 2. My solution to the POLYGON/RECTANGLE problem. > > I tend to be simple minded: > a. a RECTANGLE is a POLYGON (thefore there must be inheritance) > b. Adding a vertex to a polygon produces ANOTHER polygon (a different > mathematical object) > c. Thus, rectangle inherit the property add_vertex, which produces > a pentagone from a rectangle > > Don't flame me on this, I know this is cheating ... but it makes a lot of > sense. If one transposes the problem to applicative (rather than imperative) programming, where there are no procedures but only functions which do not produce side-effects, and also removes reference assignment (which introduces aliasing), this solution indeed works. Mirandeiffel? -- -- Bertrand Meyer bertrand@eiffel.com
rick@tetrauk.UUCP (Rick Jones) (02/07/91)
In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to rick@tetrauk.UUCP (Rick Jones): >>Given that this thread is starting to look like an Eiffel v C++ debate, there >>is a significant aspect of Eiffel which has been overlooked - assertions. > >Please don't cloud discussions with irrelevant issues. > >Inheritance and information hiding issues are orthagonal to assertion >issues. A given language may support the former, the latter, both, or >neither. Let's keep the discussions clear. A significant theme of this thread was the postulation by some that the notion of "private" was essential in order to prevent subclasses from breaking their parent's invariants. The connection between assertion and information hiding had already been made. My point is that, as far as maintaining invariants is concerned, there is more than one way to skin a cat. In fact my view is that assertions are a superior method. If "private" is used to achieve this, then information hiding and assertions are not being kept separate. If assertions are used the two issues are separated. In summary, I agree with Chip on the orthogonality, but the issue is neither irrelevant nor, it seems, entirely free of clouds. A little discussion might let in some light. -- Rick Jones Tetra Ltd. Maidenhead, Was it something important? Maybe not Berks, UK What was it you wanted? Tell me again I forgot rick@tetrauk.uucp -- Bob Dylan
craig@Neon.Stanford.EDU (Craig D. Chambers) (02/07/91)
In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes: > > [stuff deleted] > >If we take the point of view that we can add a vertex to a rectangle >with the result begin a pentagon, we now have two choices: > > [stuff deleted] > > 2. Add the vertex to the rectangle object, and change its class > from rectangle to pentagon. > > [stuff deleted] > >The second solution is not type safe in the following situation. >Suppose we have an object O with two fields: p of type polygon, and r >of type rectangle. Now suppose we execute the following code with >add_vertex changing the class of the rectangle to pentagon. > > O.r := make_rectangle(..); > O.p := O.r; > O.p.add_vertex(..); > >If we change the class of the rectangle to pentagon, then O.r now >refers to a pentagon while the class of O.r is declared to be >rectangle. If you take the view that rectangle is a specialized *implementation* of polygons, but the *type* of rectangle is still polygon, then this class-changing operation is perfectly type-safe (as I mentioned in an earlier posting). The add_vertex operation on rectangles modifies the rectangle *in place* to be a pentagon-represented polygon; the interface to the rectangle remains unchanged. This facility requires that some classes be marked as not defining new types but instead new implementations of existing types. I think that enabling the programmer to change the implementation of objects at run-time is a very powerful feature, and programmers will find new and useful ways of applying this power as they become used to thinking about an object's representation and implementation as mutable. This operation is rather complicated to implement efficiently and describe coherently in a class-based language, but is quite straightforward in a prototype-based language supporting dynamic object inheritance (delegation) instead of static class inheritance. -- Craig Chambers
schwartz@karl.cs.psu.edu (Scott Schwartz) (02/07/91)
Bertrand Meyer writes:
Mirandeiffel?
Hmmm. Maybe ML can do this, using functors. Any ML wizards reading this?
--
Scott Schwartz schwartz@cs.psu.edu
adam@visix.com (Adam Kao) (02/07/91)
Barbara Lerner writes: > The distinction between adding a vertex to a polygon and adding a > vertex to a rectangle is the following. When a vertex is added to a > polygon, no new object is created. The same polygon object exists but > has one more vertex. > If we take the point of view that we can add a vertex to a rectangle > with the result begin a pentagon, we now have two choices: > 1. Create a new Pentagon object that is a "copy" of the rectangle > with the added vertex. > 2. Add the vertex to the rectangle object, and change its class > from rectangle to pentagon. Hmm, I guess I did gloss over this distinction. I do understand the uses and problems of sharing mutable objects. Choice (1) defines nonshared or immutable use, choice (2) defines mutable use. I see these choices as complementary. Our problem with (2) is that someone else can modify the shared object in ways which I consider illegal. All users of the object should agree on the set of allowed changes. A. A Class must support all operations of its Parent Class. B. A Class must enforce all invariants that users wish to preserve on member Objects. (Allowing (2) makes Class RECTANGLE differ from our specific abstract rectangle class in the sense defined by (B). Class membership in the programming sense is actually only one of many possible invariants that we might wish to maintain to define an abstract class.) These goals conflict, which leads me to say that our notion of Class cannot support all the uses we expect. (A) and (B) are different questions; they lead to different classification schemes. If we choose to preserve (A), then Siblings will be able to change into each other. If we choose to preserve (B), then Classes specialize their Parents by reducing the allowed transformations. Any solution that tries to define a singular Class concept satisfying both (A) and (B) will be non-intuitive, at best. Adam > Barbara Staudt Lerner > Deparment of Computer and Information Science > Lederle Graduate Research Center > University of Massachusetts > Amherst, MA 01003 > blerner@cs.umass.edu PS I notice you edited the followup line to comp.lang.eiffel. Unfortunately, I know almost nothing about eiffel. I would rather continue this discussion in comp.object, or, if you feel it is more appropriate, via email.
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/07/91)
In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes: > >> From: adam@visix.com (Adam Kao) >> >[..] >> Most languages have type-casting and type-promotion. In C, we can add >> a double to an int, and the result will be a double. Is this kind of >> arithmetic forbidden in object-oriented programming? >> >> To me, the statements "p := r" and "p.add_vertex(...)" are perfectly >> compatible. The "p := r" should imply a cast; if we want to make the >> assignment, it means we want to treat rectangles as polygons. Surely >> the explicit conversion "p := r.make_polygon()" followed by >> "p.add_vertex(...)" is acceptable; why not accept even >> "r.add_vertex(...)" as a shorthand? >[..] > ... [long discussion deleted] >The second solution is not type safe in the following situation. >Suppose we have an object O with two fields: p of type polygon, and r >of type rectangle. Now suppose we execute the following code with >add_vertex changing the class of the rectangle to pentagon. > > O.r := make_rectangle(..); > O.p := O.r; > O.p.add_vertex(..); > >If we change the class of the rectangle to pentagon, then O.r now >refers to a pentagon while the class of O.r is declared to be >rectangle. *Accurately* detecting this situation at compile-time is >probably impossible. Detecting it at runtime would require every >object to maintain backpointers to all objects that reference it, so >that all class changes could be type-checked. The analysis was thorough and correct, but it assumed that variables are always references. That is indeed the case in almost all well-known OOPL's, but probably the previous poster meant _value_ and not reference assignment, as in typical non-OO procedural languages (Pascal, Ada, C, ...). Then the sharing problem inherent in reference semantics simply does not arise. Many readers of this newsgroup know that I am certainly not a C++ enthusiast, but one advantage of C++ is that it allows the programmer to declare both object-valued and reference-valued variables, depending on which is more suitable (e.g. semantically) for each situation. One drawback of C++ is that the dynamic class of an object-valued variable is always the same as its static class. Therefore, assigning (copying, converting) a subclass object to a superclass variable entails irrecoverable "type loss" (and usually other information loss too). Douglas Lea has suggested enhancements to C++ that would solve this problem to some extent; these suggestions could be implemented at least by what I would call "value semantics with reference pragmatics". In any case, in typical OOPL's (and this goes as well for C++ as for Smalltalk and Eiffel) a Rectangle class should not inherit any kind of _concrete_ Polygon class: the baggage would include not only some operations that are hardly appropriate for rectangles, but also unnecessary instance variables. One could in fact build workable software easier by making Polygon inherit Rectangle - conflicts between interface inheritance and implementation inheritance have often been described in the literature. The clean way would be to inherit from a suitably general abstract class. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/07/91)
In article <1991Feb4.223823.3198@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes: > ... >Yes I agree that abstraction is the real goal of OOD. I get the feeling >exporting features is a smoke screen. Would it not be better to simply make >all features of a class implicitly public (as in Smalltalk)? This would free >the designer from having to think about what features would be useful to >clients and help concentrate on what the interface should be in the context >of the problem. I agree with the designers of the majority of newer OOPL's that the Smalltalk way is worse. It's exactly the question about the interface. Following Ole-Johan Dahl, the grand old man of OOP, think of objects or classes offering an interface of procedures (methods) to potential clients, as an analogy of procedures (in a subroutine library, say) offering an interface of formal parameters to potential invokers. The definitions of formal parameters are important to the invokers of a procedure, but most procedures also contain local variables that are none of the invokers' business. Likewise, the definitions of public operations are important to the clients of a class, but some private operations may well be useful to implement the public ones. In most cases they could be eliminated by code duplication (contrary to the code reuse goal of OOP), but that is not possible if a private procedure is recursive. If the class designer later notes that some originally private feature should be offered to clients, he can simply change it from private to public. From the clients' viewpoint it is exactly equivalent with adding a brand-new feature to the class. Our procedure analogy does not work very well here any longer, but in some languages the formal parameter list of a procedure can be extended with optional parameters without disrupting existing code. I support the "orthogonal" view to visibility that is taken e.g. by current Simula and C++, that even instance variables can be declared public or private (or "protected"). Of course, declaring a public instance variable in a class is a strong commitment that restricts the freedom in the implementation and evolution of the class, but a class designer should have the possibility to such a commitment. _Any_ public feature constitutes a commitment and restriction, especially if its declaration promises something about its semantics (like the assertions of Eiffel) instead of containing only the signature. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
richieb@bony1.bony.com (Richard Bielak) (02/07/91)
In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes: > [...lots of stuff deleted...] >We know how to add vertices to POLYGONs. I think it ridiculous to say >we do not know how to add vertices to RECTANGLEs. The problem is, >adding a vertex to a RECTANGLE gives us something that is not a >RECTANGLE. > >I do not see this as a problem. Why do we expect that all operations >on an Object must be closed over its Class? > Perhaps the "add_vertex" routine should be a function? How about this: add_vertex : POLYGON is do -- whatever end; Then if "r" is a RECTANGLE, we can call "r.add_vertex" and get back an object that is not a RECTANGLE. ...richie -- +----------------------------------------------------------------------------+ | Richie Bielak (212)-815-3072 | "The sights one sees at times makes one | | Internet: richieb@bony.com | wonder if God ever really meant for | | Bang: uunet!bony1!richieb | man to fly." -- Charles Lindbergh |
om@icsib29 (Stephen M. Omohundro) (02/08/91)
Just a quick note on the Sather solution to the POLYGON vs. RECTANGLE problem. In Sather the distinction is made between a variable declared as "pa:POLYGON" which is guaranteed to hold an actual polygon object and "pd:$POLYGON" which can hold a polygon or any of its descendents. In Sather there is no neccessity for descendent classes to define all the features of its ancestors (and there is an "UNDEFINE" mechanism to eliminate features). If the call "pd.add_vertex" appears, then the compiler checks all descendents of POLYGON and complains if "add_vertex" is missing from any of them or has an incompatible interface. On the other hand, the call "pa.add_vertex" is perfectly legal regardless of the descendent's class definitions. If "add_vertex" is only meant to apply to actual POLYGON objects, then such declarations guarantee that they will not be applied to objects of descendent classes. In this way the use of inheritance to reuse features of a parent class in a child class is somewhat separated from its use in constraining dynamic dispatch. --
jgk@osc.COM (Joe Keane) (02/08/91)
One possibility is that RECTANGLE and POLYGON are separate concepts which have nothing to do with each other. Then there's not much to talk about. Let's assume that the RECTANGLE class is entirely an optimization. It's simply a way of doing POLYGON faster in those cases where you can. This is quite likely in a graphics application. You can do a lot of things more efficiently if you know something is a rectangle rather than a general polygon. For example, the X protocol has special cases for rectangles. Some routines will know about RECTANGLE and deal with its instances differently. Those routines which act on polygons would test if the polygon is actually a RECTANGLE instance. There may be some interface which applies only to RECTANGLE instances. For example, we may have methods which return the corners of the rectangle, or the attributes may be directly accesible. However, most methods do not know about RECTANGLE, only POLYGON. Given this, we should insist that the RECTANGLE optimization be transparent. We should be able to add a vertex to any polygon, regardless of its implementation class. That's what polymorphism is all about. Saying that it works for accessors but not for modifiers is going only half way. If we add a vertex to a RECTANGLE instance, then it will have to change its implementation class to something else. Perhaps we have a PENTAGON class or maybe it's just converted back to a generic POLYGON. Many OO languages don't deal well with this, although CLOS does. If a variable is declared as POLYGON, we can add a vertex to it. It should have no idea that the implementation class is being shifted from RECTANGLE to PENTAGON under it. On the other hand, if a variable is declared as RECTANGLE, we can't add a vertex. This is a type error and hopefully will be caught at compile time. Global type checking doesn't make sense. Either an operation is correct or it isn't. How can the definition of a class depend on how you use it? I won't even talk about the practical problem of having your type checker hunt down everyone who uses a given class.
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/08/91)
In article <1991Feb7.151212.25200@bony1.bony.com> richieb@bony1.UUCP (Richard Bielak) writes: >In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes: >> >[...lots of stuff deleted...] > >>We know how to add vertices to POLYGONs. I think it ridiculous to say >>we do not know how to add vertices to RECTANGLEs. The problem is, >>adding a vertex to a RECTANGLE gives us something that is not a >>RECTANGLE. >> >>I do not see this as a problem. Why do we expect that all operations >>on an Object must be closed over its Class? >> > >Perhaps the "add_vertex" routine should be a function? How about >this: > > add_vertex : POLYGON is > do > -- whatever > end; > > >Then if "r" is a RECTANGLE, we can call "r.add_vertex" and get back >an object that is not a RECTANGLE. This looked at first like a very good solution to me. However, it fits best if the interfaces of Polygon and other related classes are even in general defined in a "functional" fashion: i.e. modifying operations do not modify the "self" object but return a new object. If some operations modify the object itself and others return a new object instead, the interface is very inconsistent. The functional approach has its severe drawbacks, too. Suppose that there are several references from other objects to this geometric object (initially a rectangle). If the intended semantics is that the sharing relationship between these references is maintained (which would be the typical case), one would need to have back pointers to all those other objects in order to make them refer to the modified polygon. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
chip@tct.uucp (Chip Salzenberg) (02/08/91)
According to rick@tetrauk.UUCP (Rick Jones): >In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>Inheritance and information hiding issues are orthagonal to assertion >>issues. A given language may support the former, the latter, both, or >>neither. Let's keep the discussions clear. > >The connection between assertion and information hiding >had already been made. Okay, I'll grant that point. My reaction was perhaps stronger than necessary because I don't want this discussion to degenerate into "my language can beat up your language." I'm sorry if I overdid it. >My point is that, as far as maintaining invariants is concerned, there is more >than one way to skin a cat. In fact my view is that assertions are a superior >method. I can see a place for both. There are times when strict invariant checking would have caught problems in my code. The GNU C++ compiler includes a "wrapper" mechanism, which replaces calls to functions with specific return and parameter types with calls to a wrapper function, one parameter of which is the address of the "real" function. This mechanism is too limited for general assertion checking, however, because of the requirement that each set of return and parameter types have its own wrapper. I read about a proposed extension to C++ called A++, or Annotated C++, by Marshall Cline of Clarkson University and Doug Lea of SUNY. It defines assertions for the class ("legal:") and for member functions ("axioms:" with "require" and "promise" assertions). The resemblance of A++'s assertions to those of Eiffel is acknowledged in the introduction to the paper, ``Using Annotated C++.'' (Similar problems lead to similar solutions.) They hoped to avoid redundant checks when member functions call each other, and described obvious code generation tricks to that end. I wonder if they ever got their proof-of-concept compiler working...
sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/08/91)
In article <10900@pasteur.Berkeley.EDU> om@icsib29 (Stephen M. Omohundro) writes: >Just a quick note on the Sather solution to the POLYGON vs. RECTANGLE problem. >In Sather the distinction is made between a variable declared as "pa:POLYGON" >which is guaranteed to hold an actual polygon object and "pd:$POLYGON" which can >hold a polygon or any of its descendents. In Sather there is no neccessity for >descendent classes to define all the features of its ancestors (and there is an >"UNDEFINE" mechanism to eliminate features). If the call "pd.add_vertex" >appears, then the compiler checks all descendents of POLYGON and complains if >"add_vertex" is missing from any of them or has an incompatible interface. > ... An obvious implication of the above principles would be that modifications of classes and methods in a Sather environment will often cause _massive_ recompilations, wouldn't it? In particular, if some new class leaves any inherited feature F undefined, then all clients of all appropriate superclasses must be rechecked. (If the programming environment registers client relationships to the utmost detail, those client classes that invoke F through a "heterogeneous" variable - such as 'pd' above - can be pinpointed directly.) Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
blerner@empire.cs.umass.edu (Barbara Lerner) (02/08/91)
In article <1991Feb7.205232.23548@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes: > If you take the view that rectangle is a specialized *implementation* > of polygons, but the *type* of rectangle is still polygon, then this > class-changing operation is perfectly type-safe (as I mentioned in an > earlier posting). The add_vertex operation on rectangles modifies the > rectangle *in place* to be a pentagon-represented polygon; the > interface to the rectangle remains unchanged. This facility requires > that some classes be marked as not defining new types but instead new > implementations of existing types. This is an interesting solution. However, to clarify a point, it seems to me that this solution implies that clients of polygon do not know about the different implementations, but rather the system somehow chooses the correct implementation, probably based on constraints defined by the implementation. If this is true, then clients of polygon can only declare variables to be of type polygon, and not of type rectangle. (In fact, I think it would be a mistake to call these alternative implementations "classes".) Multiple implementations support the definition of algorithms of varying efficiency, but offer no client control over which implementation is chosen. (There may be faster algorithms for drawing rectangles than other polygons, but the client can't say they only want to consider rectangles.) Subtyping offers the client finer-grained control over the desired semantics, but (in strong type-checking languages) restricts the programmer's flexibility in defining subtypes. (The client can restrict a variable to be a rectangle, but now the programmer can't define add_vertex on polygons.) I suspect there are uses for both solutions. -- Barbara Staudt Lerner Deparment of Computer and Information Science Lederle Graduate Research Center University of Massachusetts Amherst, MA 01003 blerner@cs.umass.edu
dbc@cimage.com (David Caswell) (02/08/91)
.>We know how to add vertices to POLYGONs. I think it ridiculous to say .>we do not know how to add vertices to RECTANGLEs. The problem is, .>adding a vertex to a RECTANGLE gives us something that is not a .>RECTANGLE. .> I don't think that we know how to add vertices to all POLYGONs. Rectangles for example. I think the solution is to put the rectangle methods on polygon and have them assert that the number of vertices equals 4. They could be put in another file or whatever if you're worried about readability. This problem comes up in countless situations. Unfortunately there is not a perfect solution.
wright@hsi86.hsi.com (Gary Wright) (02/09/91)
In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >Inheritance and information hiding issues are orthagonal to assertion >issues. A given language may support the former, the latter, both, or >neither. Let's keep the discussions clear. At least in Eiffel, the semantics of inheritance are not orthagonal to assertions. An interesting part of this discussion has been what role assertions (pre-conditions, post-conditions, invariants) have in an inheritance model and how does that restrict the way a class can be changed with respect to its parent(s). I'd like to hear more, not less, regarding this issue. -- Gary Wright ...!uunet!hsi!wright 3M Health Information Systems wright@hsi.com
cjmchale@cs.tcd.ie (Ciaran McHale) (02/09/91)
Here's my $0.02 worth on the topic concerning rectangles and
polygons.
Some people have pointed out that since rectangle doesn't
support all the operations which might be performed on a
polygon, rectangle should *not* be a subtype of polygon.
(Other approaches were also suggested.) Instead, the graphics
hierarchy should look like:
graphic (base class)
/\
/ \
/ \
rectangle polygon
A problem with this approach is that rectangle is certainly
similar to polygon in many respects and so we would like to
have it derived from polygon so that we can reuse code.
Bascially we're torn between two seemingly conflicting issues:
1. We can't have rectangle as a subtype of polygon
since add_vertex doesn't make sense when applied
to a polygon.
2. We do not want to duplicate code which is common
between rectangle and polygon.
One way to solve this dilemma is for the language to have two
hierarchies rather than just one. There would be a TYPE
hierarchy and an IMPLEMENTATION (CODE) hierarchy. Using this
scheme, rectangle and polygon can be siblings in the TYPE
hierarchy, but rectangle could (if the programmer wanted to)
inherit code from polygon in the IMPLEMENTATION heierarchy.
TYPE HIERARCHY IMPLEMENTATION HIERARCHY
-------------- ------------------------
graphic graphic
/\ |
/ \ |
/ \ polygon
rectangle polygon |
|
rectangle
Note that the IMPLEMENTATION hierarchy does not have to
mirror the TYPE hierarchy. The IMPLEMENTATION of rectangle
will include the add_vertex operation (inherited from the
IMPLEMENTATION of polygon) but this will be "private" since
it is not specified in rectangle's TYPE.
Ciaran.
--
Ciaran McHale "Verbosity says it all" ____
Department of Computer Science, Trinity College, Dublin 2, Ireland. \ /
Telephone: +353-1-772941 ext 1538 FAX: +353-1-772204 \/
Telex: 93782 TCD EI email: cjmchale@cs.tcd.ie
bertrand@eiffel.UUCP (Bertrand Meyer) (02/10/91)
** Note: As Markku Sakkinen requested before, it would be nice if everyone left the original groups in the Newsgroups line. Someone, for some reason, had removed comp.lang.eiffel. I have put it back. ** end of note. This is simply to point out that the issue of ``implementation reuse'' versus ``specification reuse'' has nothing to do with this whole discussion. The debate (e.g. my original questions regarding polygons and rectangles) can be entirely confined, if need be, to the purely mathematical notion of abstract data types. We can talk about inheritance relations as being morphisms in categories, and we don't even need to refer to computers. I should add that ``implementation reuse'' is, for me, a term devoid both of meaning and of interest. The separation between concept and implementation in software is an historical evil, not a desirable situation. With a good notation we can remove the difference. For one thing, this is clearly the goal of Eiffel. But of course the human tendency to make things difficult when they should be simple is eternal. I also know that there is very little chance that anyone beyond Eiffel users will accept this today. It will take several years; too many people have been taught that implementation is dirty and ``specification'' or ``design'' is good. (See the lengthy letter in the January Communications of the ACM, by a gentleman from the US Coast Guard, claiming that he represents the trenches of the computing profession and, among other things, that software engineering has nothing to do with implementation.) Of course if one's world is an environment where implementation is done in Cobol or C, the need for something at a higher level is obvious. But this is irrelevant to object-oriented technology, or software engineering for that matter. Somewhere in his essays on linguistics, Roman Jakobson illustrates a point by telling the following story. A missionary was blaming natives because they always went everywhere naked. ``Cover your body!'', he would scream. Once they asked back: ``But you too have an area of uncovered flesh, right there, haven't you?''. ``Of course'', said the Father, ``but that's different! It's my face''. ``Well then'', retorted the natives, ``with us, everything is the face''. I like to think of this as a guide for software construction. One day we will stop being ashamed of implementation, not because we will have removed the need for implementation but for exactly the opposite reason: with us, everything is the implementation. -- Bertrand Meyer bertrand@eiffel.uucp
cjmchale@cs.tcd.ie (Ciaran McHale) (02/11/91)
This discussion has used a concrete example (involving polygons, rectangles and an add_vertex operation) to discuss an issue. From the top of my head I can recall the following approaches being suggested: o Having the add_vertex operation in polygon makes that class too specialised to be used as a superclass for rectangle. Reorganise the class hierarchy a bit to solve the problem. o The problem can be solved if the language allows an object to change its type at run-time. Thus applying add_vertex to a rectangle changes that object into a (pentagon or a) polygon. o Rectangle.add_vertex should be a null operation. o Rectangle.add_vertex should raise an exception. o Have very fine grain multiple inheritance. o Have a read_only interface to each class. The read_only interface for polygon would exclude add_vertex, and this resultant class is then suitable as a superclass for rectangle. (I'm sure that other approaches were discussed too.) Then I add in my $0.02 worth suggesting: o If a language has separate type and implementation hierarchies then rectangle can reuse code from polygon without having to support add_vertex in its type. Bertrand Meyer (article <498@eiffel.UUCP>) replies with: >This is simply to point out that the issue of ``implementation >reuse'' versus ``specification reuse'' has nothing to do >with this whole discussion. Why not? It _is_ an approach to solving the problem at hand. It may not be the best approach but I don't see why you are so eager to dismiss it out of hand. >The separation between concept and implementation in software is >an historical evil, not a desirable situation. With a good notation we >can remove the difference. >[...] >One day we will stop being ashamed of implementation, >not because we will have removed the need for implementation >but for exactly the opposite reason: with us, everything >is the implementation. I don't know how (in)correct you are. Only time will tell. But just because you feel that way does not mean that others share your beliefs. So until separation-between-type-and-implementation is widely accepted to be a bad idea, why not consider it where it might be useful? Ciaran. -- Ciaran McHale "Verbosity says it all" ____ Department of Computer Science, Trinity College, Dublin 2, Ireland. \ / Telephone: +353-1-772941 ext 1538 FAX: +353-1-772204 \/ Telex: 93782 TCD EI email: cjmchale@cs.tcd.ie
weigand@kub.nl (Hans Weigand) (02/11/91)
In article <494@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes: >... >This does not work, since the new precondition would be stronger (again, >see above references). It would make it impossible for a client Perhaps the problem discussed here hinges on an inappropriate definition of the precondition. Usually, preconditions are thought to be *nessecary* conditions, and, moreover, the implicit assumption is that when all necessary conditions have been specified, these together are also sufficient. In this conception, preconditions can be inherited and strenghtened for subtypes. However, if a precondition is assumed to be sufficient, as Eiffel does, rather than necessary, then the inheritance runs counter to the usual line of specification. That is, the programmer first specifies the supertype, and specifies the preconditions that he deems necessary. However, when he later comes to subtypes, he typically wants to add more specific preconditions. In the "sufficiency" approach, the programmer should be aware of all subtypes details when he is designing the supertype, which is not reasonable. Perhaps you say that the "sufficiency" approach follows from the contract view, but I doubt. When I make a contract for X, and state my conditions (the obligations of the client), I do not commit myself more than needed. If the client wants to use my product Y too (the subtype), then I would like to be able to negotiate new conditions. These conditions then add to the first ones. Reference: Wieringa R, H Weigand, JJ Meyer, F Dignum: The Inheritance of Dynamic and Deontic Integrity Constraints. Annals of Mathematics and Artificial Intelligence, Nov 1990. -- Hans Weigand Tilburg University
paj@mrcu (Paul Johnson) (02/11/91)
In article <1991Feb7.205232.23548@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes: >In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes: > [Good stuff about the polygon-rectangle problem deleted. Briefly, > the idea was that rectangle.add_vertex turns the object into a > polygon] In passing, its worth noting that the InterViews solution to this very problem was similar to what was proposed in the referenced articles. If you asked for a rectangle, you got a polygon with 4 vertices and all corners 90 degrees. This could then be rotated and stretched at will. Paul. -- Paul Johnson UUCP: <world>!mcvax!ukc!gec-mrc!paj --------------------------------!-------------------------|------------------- GEC-Marconi Research is not | Telex: 995016 GECRES G | Tel: +44 245 73331 responsible for my opinions. | Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244
Bruce_Atherton@mindlink.UUCP (Bruce Atherton) (02/14/91)
I have some thoughts to add on this debate regarding how to deal with a RECTANGLE and the add_vertex call. When trying to work these questions out, I usually rely on real world examples, so please bear with me. Consider a gardener who is told that to prune plants you have to cut some branches off. He is given the task of pruning a garden, so he goes to each plant and does so. What does he do when faced with a cactus? It has no discernible branches, yet it is clearly a plant. He could cut away at it anyway, but this is stupid behaviour. He could just ignore it, but if the cactus required special care instead of pruning, this would not be smart either. The best solution would be for the gardener to report to whoever gave him the task that he could not prune a cactus. This seems to be analogous to the RECTANGLE-add_vertex debate. The correct action would be to report an error. Bertrand Meyer pointed out that this would alter the terms of the contract, but I don't think this is necessarily correct. It could be a fundamental part of the language that a contract can be breached (to continue the metaphor). The action taken when an object is in breach of contract is up to the object that is contracting the services. I don't know whether ALL contracts should be breachable. After all, there are some things that are so fundamental to our understanding of a polygon that we can't imagine a polygon without them. On the other hand, just because we can't imagine one doesn't mean that someone who reuses the class can't. I am not a language designer, nor have I paid much attention to such details in the past. I am just an end user of these languages. It may well be that allowing a contract to be breached invalidates some model that is used in object-oriented language design. If it does, though, in my opinion there is something wrong with the model. It is often required that a subclass eliminate a property of the more general class. A language which does not provide this facility is not meeting my needs as a user. And isn't that what it is all about? -- UBC Faculty of Law Artificial Intelligence Research Project Bruce_Atherton@mindlink.UUCP or| "You fell for the second most famous blunder. uunet!van-bc!rsoft!mindlink!a35| The most famous is never to get involved in a land war in Asia." - The Princess Bride
simons@tetrauk.UUCP (Simon Shaw) (02/18/91)
In article <4810@mindlink.UUCP> Bruce_Atherton@mindlink.UUCP (Bruce Atherton) writes: >I have some thoughts to add on this debate regarding how to deal with a >RECTANGLE and the add_vertex call. >... > The correct action would be to report an error. > >Bertrand Meyer pointed out that this would alter the terms of the contract, but >I don't think this is necessarily correct. It could be a fundamental part of >the language that a contract can be breached (to continue the metaphor). The >action taken when an object is in breach of contract is up to the object that >is contracting the services. > > ... It is often required that a subclass eliminate >a property of the more general class. I have been lurking here for some time, and am very keen that a satisfactory answer should be found to the inheritance debate. The above seems to me to hit the central idea of the discussion, i.e. 1. Inheritance is about specialisation. 2. Specialisation is often a _restriction_ of the range of allowed circumstances. 3. Ergo, not all the features of a parent class can necessarily be implemented on a child object. The strongest refutation of this line of argument seems to be the one which says "If this problem happens, you have got your class design wrong", (e.g. the solution of FIXED_POLYGON v. VARIABLE_POLYGON) and the subsequent discussion concentrated on how to find the error (assertions, static checks). But - can a change in class design be _proved_ to be possible in all cases, (or proved to be impossible in any case, which is the same thing). If I've missed something important, flame away ! -- Simon Shaw ; simons@tetrauk.uucp ; net.lurker
jnw@shades.cis.ufl.edu (Joseph N. Wilson) (02/20/91)
I was directed to this discussion (indirectly) by Chris Clifton of Princeton. In article <1101@tetrauk.UUCP>, simons@tetrauk.UUCP (Simon Shaw) writes: [...] |> 1. Inheritance is about specialisation. |> |> 2. Specialisation is often a _restriction_ of the range of allowed |> circumstances. |> |> 3. Ergo, not all the features of a parent class can necessarily be |> implemented on a child object. |> [...] |> Simon Shaw ; simons@tetrauk.uucp ; net.lurker This sort of problem is evident even in languages with no explicit inheritance mechanism. Consider integer subranges of Pascal. Addition of two values belonging to some subrange a..b does not necessarily yield a value in a..b. It is not reasonable to assume that all properties of an algebra will hold for a similar subset algebra inheriting values and operations. joe wilson (jnw@cis.ufl.edu)
ogden@seal.cis.ohio-state.edu (William F Ogden) (02/20/91)
Simon Shaw writes: ... >I have been lurking here for some time, and am very keen that a >satisfactory answer should be found to the inheritance debate. ... Doesn't it strike anyone that there really isn't a satisfactory answer to the polygon/rectangle inheritance problem. If inheritance weren't a central tenet of the Object Oriented Programming dogma, but just an ordinary idea, we could take a detached scientific view and discard it as just another initially attractive notion that when elaborated in full generality proves bankrupt. /Bill
ian@syacus.acus.oz.au (Ian Joyner) (02/25/91)
sakkinen@tukki.jyu.fi (Markku Sakkinen) writes: >In any case, in typical OOPL's (and this goes as well for C++ as for >Smalltalk and Eiffel) a Rectangle class should not inherit any kind >of _concrete_ Polygon class: the baggage would include not only You could consider a rectange as a specialised parallelogram. In this case the rectangles invariant checks the added restriction, that the rectangles angles are all 90 deg, whereas the parallelogram must only check the opposite angles are equal, and they add up to 360 deg. However, a parallelogram is a concrete class from which rectangle can inherit, as all rectangles are parallelograms. A square may multiply inherit from a rectangle and a rhombus. In this case it does not need to extend the invariant, as the essential properties of all sides being equal are inherited from rhombus, and all angles being 90 deg are inherited from rectangle. -- Ian Joyner ACSNet: ian@syacus.oz ACUS (Australian Centre for Unisys Software) DNS: ian@syacus.oz.au Tel 61-2-390 1328 Fax 61-2-390 1391 UUCP: ...uunet!munnari!syacus.oz