Path: utzoo!attcan!uunet!tut.cis.ohio-state.edu!cs.utexas.edu!usc!julius.cs.uiuc.edu!rpi!uupsi!sunic!news.funet.fi!tukki.jyu.fi!sakkinen From: sakkinen@tukki.jyu.fi (Markku Sakkinen) Newsgroups: comp.object Subject: Re: prototypes and degenerate cases Message-ID: <1990Sep16.083629.10936@tukki.jyu.fi> Date: 16 Sep 90 08:36:29 GMT References: <1990Sep13.235832.23454@Neon.Stanford.EDU> Reply-To: sakkinen@jytko.jyu.fi (Markku Sakkinen) Organization: University of Jyvaskyla, Finland Lines: 138 In article brucec@phoebus.phoebus.labs.tek.com (Bruce Cohen;;50-662;LP=A;) writes: >I've just been reading the Self papers, and it occurred to me that using >prototypes for inheritance could solve a problem which has been annoying me >for quite some time. Maybe some of you out there coould comment on the >theory and/or practice of the idea. I don't think that the core of your problem is dependent on whether the object model is based on classes or prototypes. I'll try to explain why. >Consider an object-oriented graphic drawing system, in which there's a >general shape object, a polygon object, a rectangle object, a circle >object, and an ellipse object. One way you could arrange class inheritance >for such objects is like this: > Shape > | > ------------ > | | > Polygon Ellipse > | | > Rectangle Circle >That is, the root class is an abstract shape class, and it's subclasses are >the most general concrete classes, with more specific (i.e., degenerate case) >classes inheriting from them. [...] > ... >But what happens when objects can change over time? How do we dynamically >mutate a rectangle to a polygon and back again while retaining the cost >savings of the more refined object? [...] > [...](this is the >technique you would have to use in C++). The mutator would create (e.g.) a >new degenerate object, copy the relevant part of the state of the >non-degenerate one, then install the new one in the right place and >manually update references to the old object. Unfortunately this idea does not quite work at least in C++: there is no way to find all references and pointers to a given object. A more fundamental problem is that inheritance in almost all OOPL's binds together the interface and the implementation of classes. This problem has often been discussed in the literature (e.g. by Alan Snyder). If Circle is defined as a subclass of Ellipse, then every Circle object must contain at least all instance variables of Ellipse, although it would actually need less. Note that Circle is not strictly a subtype of Ellipse but only a "read-only subtype": we can modify the axis ratio of an ellipse, but if we try to do that to a circle, it ceases to be a circle. There are two main ways to look at the subclasses of each most general concrete class: either we want them to be very visible to clients and have an enhanced interface (e.g. additional operations applicable to rectangles but not to other polygons), or we want them to be merely more efficient implementations of special cases and have exactly the same interface as their superclasses. It seems from the preceding paragraph of the original posting that your goal is the latter, so I'll first propose a solution to that. (In fact I'll postpone the discussion of the former case to a later posting, to keep this one at a reasonable size.) Advertise only the classes Polygon and Ellipse to potential clients, but define for them perhaps only one instance variable, which will simply reference an object of an "implementation class" that is utilised behind the scenes. All operations requested by clients from a Polygon or Circle object are simply forwarded to the implementation object. For the small example given we need as implementation classes: GeneralPolygon, Rectangle, GeneralEllipse, Circle. These shall _not_ be subclasses of the "front end" classes Polygon and Ellipse, but it could very well be expedient to make GeneralEllipse a subclass of Circle! (Yes, the inheritance hierarchy of implementation classes often gets inverted.) The main functions that a "front end" object must then fulfill are: - to create the implementation object after its own creation (this goes easily in C++ and other languages that have constructors); - to exchange the current implementation object into one of another class when needed; - to delete the implementation object in conjunction with its own deletion if there is no garbage collection (thus e.g. in C++, where this is fortunately easy to implement with destructors). >Suppose instead that the objects inherited from prototypes, and the mutator >merely changed the appropriate parent pointers to (e.g.) remove a >degenerate object's redundant slots and change it's behavior to the less >costly method. [...] This reasoning seems to me to be incomplete. If the programming technique (in a prototype-based language like Self) is such that the parent objects (Shape, Ellipse, Circle, ...) contain the methods and the instance objects only the data, then the _behaviour_ can of course be changes by changing the parent pointer. But that does not magically remove the now superfluous instance variables from the object! If that is wanted, the object itself must somehow be modified just as in the class-based case. In the opposite situation that a circle needs to become an ellipse it is _necessary_ (and not just a matter of efficiency) that the additional fields be added to the object. In this style of programming, Shape, Ellipse, etc. are simply classes in disguise. A clear disadvantage to real class-based programming is that one can connect a child to a parent even if the child has not got all those instance variables that the parent's methods presuppose. > ... In article <1990Sep13.235832.23454@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) replies: > ... >To mutate a circle into an ellipse, a new ellipse object would be >created with the appropriate state, and the parent of the child object >would be redirected to the ellipse object from the circle object. All >clients still point to the child, and so the child appears to have >"become" an ellipse (or at least its external behavior has changed so >that its two foci are no longer the same point). But no weird >operations (like mutating the formats of objects or redirecting all >external pointers) are happening, just a single assignment to a single >(parent) slot. Dynamic inheritance is *much* more structured and >clean than become:. The main reason for having a Circle class at all was to have a compact and effective representation for an important special case. According to this suggestion, when a circle becomes a general ellipse, there will be _both_ a circle object _and_ an ellipse object. And when an ellipse becomes a circle, there are no storage savings; at best, the parent objects can be defined so that creating a new circle object (!) is not necessary. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) Seminaarinkatu 15 SF-40100 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)