From: Bob Hathaway <objsys@netcom.com>
Subject: Re: O.M(...) vs M(...), and is the Real World O-O?
Organization: Object Systems
Date: Fri, 2 Oct 1992 20:08:03 GMT

In article <1992Sep27.140932.2792@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>>If the downcast fails,
>>all is well because f won't be able to access a Base's protected members.  In
>>other words, all is well because downcasting obviously enforces interface
>>access specifications.
>
>	I do not agree that it is ok to access the interface
>of an object based on its actual type. The correct method for accessing
>members based on the run-time type of an object is by virtual functions.

This is only one (quite restricted) way.  Smalltalk provides the opposite
by allowing any method to be passed.  There is a middle ground and I think
that should be preferred.  I suggest reading up on some dynamically typed
OO languages for breadth. 

>	When I create and abstract base class, and then write routines
>which manipulate that base class, I expect those routines to be
>able to manipulate the objects via the abstract interface only.
>A routine so written will work for ANY derived class (provide that
>derived class has the proper semantics), and that is the basis
>of polymorphism.

That is the basis for a limited variety of polymorphism; I think you're
leaving out the most important and general cases.  Many environments
*require* more than the statically typed variety.

>>And using virtuals assumes all functions of all derived
>>classes are moved into the base class.  This is most undesirable with the
>>most obvious reason being an unnecessary coupling of Base and Derived
>>classes (and there are many more).
>
>	If you get this problem (fat interface) you have a lousy design.
>And you're still thinking in terms of single inheritance. Anyone

No, my point applies to MI as well.  And you're ignoring the most
important cases again.  I think you've grasped the C++ virtual function
mechanism well but I suggest reading up on something like Coplien
to cover the rest; I think you're about 1/4 the way there...

>>Not at all.  For example, a device class can be declared.  Certain kinds of
>>devices may have certain operations and certain kinds may not.  Downcasting
>>is *one* way of accessing these derived operations without moving all derived
>>class functions into the base class as virtuals.  Another solution is to
>>dynamically query if a message is available in a polymorphic object and only
>>invoke it if it is; this is common in Objective-C.
>
>	And BOTH of these would be bad design for C++ in my books.
>In C++ you have to know the interface you intend to use statically.
>Only the implementation is allowed to be dynamic (via virtual
>functions).

Again, Coplien.  There are many general cases where this kind of flexibility
and greater power are required.  Try not to get stuck on just what the C++
language provides as primitives since these were only added to C++ if
they didn't add any non-trivial overhead and not because they were the required
or most desirable facilities.  Coplien covers many cases in which C++ is
intended to be flexible enough to cover but only with extended "idioms".

>	If you have, for example a base class 'stream' device driver,
>and then you have 'serialport' and 'diskdrive' then it is true
>that for example disk files can be 'rewound' to the start,
>whereas serial ports can have their baud rate changed.
>
>	If you write a routine using 'stream' it must not depend
>on the specific device type. ONLY on the attributes of all
>streams. That is called device independence.

Ok, good case for abstract classes (or virtuals).  I didn't mean to infer
this case doesn't exist, just that it doesn't cover the entire spectrum .

>	Suppose you downcast to 'serialport' or 'diskdrive',
>so as to, say open the stream correctly. Thats ok in
>your books isnt it?

No, in this case you would only use the base interface.  Placing the classes
inside of a container which defines the only public interface is a solution to
this problem (the envelope/letter idiom), at least without direct language
support.  Just because more powerful facilities are added to a language doesn't
mean the less powerful facilities are no longer useful, they simply provide
greater facility when needed (which is not in this case).

>	But you just wiped device independence. Because now
>I add 'video' to the devices, and your routine fails.
>In by books, sticking to the declared interface
>guarrantees polymorphism.

But precludes more demanding applications.  Just use the facilities as
appropriate and you avoid problems.  Again, the envelope/letter idiom
can be trivially used to support this enforced idiom, so do you still
profess there should be language enforcement?  I think there must
be the alternative of greater flexibility which leaves the choice between
adding language support for strict base specification access or just
settling for idioms.

>>>>While messaging is usually Ok
>>>>it doesn't cover these idiosyncratic cases.  
>>>
>>>	In other words you want to have a large set of derived classes,
>>>most of which are subtypes, but a few are not quite subtypes, so you
>>>put special tests in for these few cases. IMHO there must be a better
>>>way, for example, tagged pointers.
>>
>>Not at all.  In the device example there are many kinds of devices and
>>they do not all share the same operations.  And tagged pointers
>>can be used to implement downcasting, at least by my use of the term.
>
>	Tagged pointers allow SELECTION. From a statically
>determined set. That set must be declared ***statically*** in advance.
>Downcasting allows checking on any type.

Downcasting must be statically declared in advance too, I don't see your
point.

>Tagged pointers are completely statically type safe and sound.
>Checked downcasting is safe, but not IMHO sound.
>
>	Tagged pointers are used to implement heteromorphism,
>which is the opposite of polymorphism. Hetermorphism manages
>similar looking interfaces compactly, polymorphism requires identical
>interfaces.

Why are you claiming this?  Smalltalk has *no* interface enforcement
but is still polymorphic, in fact even much more so than in statically
typed casses.  Could you explain "heteromorphism"?

>>>>Not being able to access
>>>>derived class interfaces is too severe a restriction; I think, and really
>>>>hurts for the most general cases too.  
>>>
>>>	This is a value judgement about a trade off, and you might well
>>>be right.
>>
>>Not really.  In (dynamically typed) Smalltalk you can take any object and
>>pass it any message; this is lost with only static typing (types only
>>available at compile time) or is at least a restricted mess.
>
>	So you lose power. Smalltalk dynamism is powerful. I agree.
>It is also very hard to catch errors at compile time. So
>its hard to be sure your code works. C++ strong typing removes
>from existance a whole class of errors (type errors) at compile time.
>I do not see how you fail to perceive this as a trade off of
>power against correctness. Given a certain task, one has to
>chose which is more important, to decide to use Smalltalk or
>to use C++ for that job. Thats a value judgement.

Certain applications *require* the flexibility of dynamics, in other
words there are domains where static typing doesn't work and you have
no choice.  The two are not mutually exclusive.

>>>	class Q {
>>>	public:
>>>		void enQ(X&);
>>>		X deQ();
>>>		int nQ()const;
>>>	};
>>>
>>>A queue manager routine has calls
>>>
>>>	getsome(Q&); .... putsome(Q&);
>>>
>>>where 'getsome' and 'putsome' are user supplied source and sink routiines.
>>>Now the Q manager is supposed to preserve the order of things in the queue.
>>>But as it stands, getsome or putsome might scramble the order. So we
>>>change the Q class---
>>>
>>>	class Qbase { public: int nQ()const ...}
>>>	class NQ : public virtual Qbase { public: enQ(X&); };
>>>	class DQ : public virtual Qbase { public: X deQ(); };
>>>	class Q : public virtual NQ, public virtual DQ {};
>>>
>>>Now I can declare
>>>
>>>	getsome(NQ&);
>>>	putsome(DQ&);
>>>
>>>and, without downcasting these routines cannot scramble the Q.
>>>With downcasting, they can.

Ok, now I see the point.  Envelope/letter works just fine here by avoiding
inheritance in the choice of specification but this is a valid argument
for including some kind of "enforced" interface.  This does not infer;
however, that downcasting or non-strict interfaces should never be used
since I have alluded to many cases in which it should.

bob
objsys@netcom.com
Newsgroups: comp.object,comp.lang.smalltalk,comp.lang.eiffel