Topic: Covariant Types in Derived Classes
Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sat, 8 Aug 1992 20:25:45 GMT Raw View
In article <PPS5VKL@netmbx.netmbx.de> jrobie@netmbx.netmbx.de (Jonathan Robie) writes:
>
>I do not understand your argument. An OODBMS can work perfectly
>well with C++ types created in the normal way. You say that this
>is not a complete solution because it is not reflective, and then
>say that a reflective system is a bad idea. Therefore it would only
>be a complete solution if it violated type safety?
Of course: even an INCOMPLETE solution like POET violates
principles of soundness like encapsulation, type-safety, and any
thing else you can think of.
A user of POET cannot rely on C++ to ensure POET does not
do something naughty, e must rely on POET and its vendors directly
not to take unfair advantage of having this access.
For example, POET must surely store private data members
with an object---POET is in effect a friend of every persistent
class. Futhermore, POET automates object construction from the
persistent store, thus non-member functions can create objects.
Almost any attempt to use RTTI is, by definition, an
escape from the statically verified type system because that system
is inadequate for dynamic operations to be performed automatically.
Only a language implemented persistent object storage scheme could
be sound.
>
>It seems to me that a OODBMS for C++ should store and retrieve C++
>objects without making fundamental changes to the concept of object.
POET may or may not be secure---I would have to read
all the code to find out (and even then I wouldn't really know :-)
It is like a gun: a sound system wont let you have a gun.
If you have a gun, you might well use it wisely, and you might
not, but the system is no longer secure in either case.
>
>We take your objects and store them. Only an OODBMS can do this.
>This is a complete solution for C++; a complete solution for
>CLOS, Smalltalk, Self, etc. would have to fully support the data
>models found in those languages.
>
But you have a small confusion here: C++ does not need
an OODBMS, indeed, not understanding Disk drives as a concept,
couldn't have one if it wanted, nor could it even know how to
want one.
It is end users that need OODBMS: the programmer
merely uses it to satisfy client demand.
Most DBMS users today use relational databases,
some written in C. Those that are supplied for personal
computer users do not require programmers for simpler tasks,
yet can create new types, while running, at user request.
One selects the 'CREATE' menu entry, paints a screen, fills
in details of the fields, and, presto one can start entering
data and generating reports.
Even the underlying programmer accessible DBMS engine
can do this.
Until OODBMS can also do this, it is not a complete END USER
solution. And until the underlying OODBMS engine can do this,
it is not amenable to a developer writing such a complete solution.
This is one of the BIG reasons you might want RTTI:
with an amenable format you could operate on BOTH programmer supplied
classes and ALSO construct new ones while running, by building
an RTTI-like picture of the new object, and generating operations
by using and interpreter, or, better, compiling and dynamically liking
machine code.
Actually, dynamic linkage and compilation is not so hard:
I'll bet you could implement it with suitable operating system
support. Even Windows :-) could easily support this.
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Tue, 4 Aug 92 14:09:49 GMT Raw View
In article <712886003snx@trmphrst.demon.co.uk> nikki@trmphrst.demon.co.uk
(Nikki Locke) writes:
>
> In article <CXK52MG@netmbx.netmbx.de> jrobie@netmbx.netmbx.de (Jonathan
Robie) writes:
>
> > nikki@trmphrst.demon.co.uk (Nikki Locke) writes:
> > >So, as I understand it, you want some kind of RTTI which enables you to
> > >determine not only the type (class) of any object, but also details of
> > >every public and private variable and implementation details within the
> > >class. Can this really be true ? If it is, you don't want C++ :-) I must
> > >be mis-interpreting, surely.
> >
> > Maybe I am corrupted by working on object oriented database systems which
> > need to store and retrieve objects without forcing the user to tell us what
> > the objects are. We do in fact need the kind of info you describe. There
> > are other systems which also need this kind of info, as I have pointed out
> > in previous messages.
> Writing an OODB is definitely a special case. Even so, the OODB should not
> be allowed to totally ignore encapsulation. The programmer writing a class
> should be able to state which parts of their object they want you to store
> on your database, and which they don't. They should also be able to
> intercept the store and retrieve process on a class-by-class basis, so
> they can implement internal caches, compression, encryption or whatever.
> So complete RTTI is not the correct (IMHO) solution to your problem, as it
> doesn't allow this level of control.
>
A complete RTTI is the first step toward the solution. It makes the final step
possible: to build up a type management library (TML) based on the type
identifier and the type specifications (not necessarily for all types)
generated by the compiler.
> The key word in the above is "programmer". In order to maintain
> encapsulation, the programmer who writes a class should have complete
> control over which parts of the class are accessable from outside.
> So, once again, complete RTTI is not the correct (IMHO) solution to this
> problem, as it breaks encapsulation.
>
TML does not necessarily to expose all data members, especially those private
and protected ones, to the outside of a class. For those it should, it can ask
for the privilege.
David Shang
Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Thu, 6 Aug 1992 00:22:30 GMT Raw View
In article <1992Aug4.140949.18731@cadsun.corp.mot.com> shang@corp.mot.com writes:
[OODBMS discussion deleted]
>A complete RTTI is the first step toward the solution. It makes the final step
>possible: to build up a type management library (TML) based on the type
>identifier and the type specifications (not necessarily for all types)
>generated by the compiler.
This is not a complete solution by a long shot.
A complete solution requires a fully dynamic reflexive system,
which can CREATE new types while running.
That can be done in Lisp I think, and maybe Smalltalk.
If you really want that kind of power you have to give up the
safety that comes from a static compiled language.
>
>TML does not necessarily to expose all data members, especially those private
>and protected ones, to the outside of a class.
>For those it should, it can ask
>for the privilege.
I think it must be the other way: the class must GRANT
priviledge. In particular, if a base class GRANTED in its interface
permission to all users to downcast to a fixed declared
set of derived classes, then legal downcasts would not break
the open/closed principle I think.
That is because closure of the base class would imply
or require closure of the listed derived classes.
You see that such a simple thing might fix some problems
and not others, in particular you would have to modify the base
and recompile some code to allow an new derived class to be
downcast to. (or, type checked, which is more or less the same
thing).
This fact seems to me to imply that this finiteness
of predeclared downcastable bases with necessry recompilation
is precisely what is REQUIRED to preserve the open.closed
principle, and thus that any attempt to make the
list arbitrary would thus be unsound.
Typically, ex-Smalltalk/Lisp programmers have problems
with C++ because they do not see the restrictions static systems
impose as being advantages. There are many of us that will not
give up those advantages for more dynamism, since C++ is one
of the few systems (Eiffel is another) that offer assurances
like type-safety that dynamic systems, BECAUSE of their greater
expressive power CANNOT.
This is NOT to say that all downcasting or RTTI is
not sound, only that, at least from my point of view,
you should stop trying to convince me of the advantages
of RTTI and try to help me calculate what limitations
must be imposed on it to make its use SOUND.
After we have a sound mechanism, THEN you can convince
me that I should use RTTI instead of some other mechanism.
For example, see Sasha's recent post which presents
downcasting as part of a paradigm of re-concretizing after
abstraction. I dont necessarily agree with it, but it
DOES offer an initial conceptual framework in which
downcasting is not a hack.
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: jrobie@netmbx.netmbx.de (Jonathan Robie)
Date: Thu, 6 Aug 1992 07:36:39 GMT Raw View
maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>In article <1992Aug4.140949.18731@cadsun.corp.mot.com> shang@corp.mot.com writes:
>[OODBMS discussion deleted]
>>A complete RTTI is the first step toward the solution. It makes the final step
>>possible: to build up a type management library (TML) based on the type
>>identifier and the type specifications (not necessarily for all types)
>>generated by the compiler.
> This is not a complete solution by a long shot.
>A complete solution requires a fully dynamic reflexive system,
>which can CREATE new types while running.
> That can be done in Lisp I think, and maybe Smalltalk.
>If you really want that kind of power you have to give up the
>safety that comes from a static compiled language.
..........
> Typically, ex-Smalltalk/Lisp programmers have problems
>with C++ because they do not see the restrictions static systems
>impose as being advantages. There are many of us that will not
>give up those advantages for more dynamism, since C++ is one
>of the few systems (Eiffel is another) that offer assurances
>like type-safety that dynamic systems, BECAUSE of their greater
>expressive power CANNOT.
I do not understand your argument. An OODBMS can work perfectly
well with C++ types created in the normal way. You say that this
is not a complete solution because it is not reflective, and then
say that a reflective system is a bad idea. Therefore it would only
be a complete solution if it violated type safety?
It seems to me that a OODBMS for C++ should store and retrieve C++
objects without making fundamental changes to the concept of object.
We take your objects and store them. Only an OODBMS can do this.
This is a complete solution for C++; a complete solution for
CLOS, Smalltalk, Self, etc. would have to fully support the data
models found in those languages.
Jonathan
=======================================================================
Jonathan Robie jrobie@netmbx.UUCP
Arnold-Zweig-Str. 44 jrobie@netmbx.in-berlin.de
O-1100 Berlin
Deutschland Phone: +37 (2) 472 04 19 (Home, East Berlin)
+49 (30) 342 30 66 (Work, West Berlin)
--
Jonathan
===========================================================================
Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Fri, 17 Jul 1992 15:12:05 GMT Raw View
In article <1992Jul15.192628.1891@cadsun.corp.mot.com> shang@corp.mot.com writes:
>> In article <1992Jul14.192525.24284@cadsun.corp.mot.com> shang@corp.mot.com
>writes:
>>
>> >The first one is to view run time type check as an overhead. It is not. It
>> >is a necessity.
>
>In article <1992Jul15.130307.28112@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John (MAX) Skaller) writes:
>>
>> I would say this backwards: IF it is necessary,
>> THEN you must incur the overhead.
>>
>
>When I say it is necessary, I don't mean it is always necessary. But as a
>general purposed language, it should include a facility when it is necessary in
>some *common* case.
OK. Suppose I accept this argument. I need then to
see a common case. You assert that het. aggregates would
benefit from this but I assert they are never required.
Therefore, from a given design requirement you need to
show that it can be done no other way.
I agree there might be such cases, by the way.
[Exceptions appear to be one of them, for example!]
But I have not seen such a case.
>
>> >It is the common case that you have to run time check whether an
>> >index is overflow the range of a array.
>>
>> On the contrary, in C++ it is the case that you CANT do
>> a run time type check on an array! (At least
>> not automatically using built in arrays)
>>
>
>What C++ can't do is not what the real world should not have.
Yes, and what C++ doesn't yet have might not have to
be what it never has :-)
So we have similar goals here but I am not convinced.
>Besides, I am not
>talking about automatic run time index check.
This is a case where we MIGHT be able to have checking
because there is no other way. But that is another story.
>
>> >And it is also a common case that you
>> >have to run time check what the type of the object is in order to perform
>> >the appropriate operations,
>>
>> It is not common in my programs. I have never had to do
>> this.
>>
>
>Be realitic, please. What you have not is not what others have not either.
I am being realistic. I do not have this need because I do
not design systems that require heterogeneous aggregates.
I aspire instead to design systems using mixin techniques,
which replace the concpet of 'heterogeneous' aggregates with
that of 'anonymous' types.
Now by say keeping a master array of all objects,
and arrays of pointers (all of the same base class),
the heterogenaety is obtained without ANY run-time type
operation: it is obtained at object contruction
time by the maker of the object (who is the only
one who knows the actual type, and who dies shortly
after the object is created)
Suppose for example you have abstract base classes
A1, A2, ... AN.
You make an object by mixing the abstractions
class A : public virtual A3, public virtual A7 { ..
And a concrete example by
class D : public virtual A, .....
Now you say
D* d=new D;
And you can add the pointer to arrays of pointers to A3 and A7.
[Also a root owner class perhaps to control object life,
perhaps with reference counting ? ]
NOW if you want all the objects with property A3, you already
have them.
>
>> >especially for heterougeneous data structures.
>>
>> Yes, so if your design is base on heterogeneous aggregates,
>> it is probably faulty. C++ can't handle them, and I argue MOST of the time,
>> they are not required.
>>
>
>Now, you see what you have not seen before.
I have seen many people try to write C++ this way.
It can only bring grief. There are other ways to handle systems
of objects of many types than lumping them all togther in
a basket, forgeting what type they are, then asking later,
"What type are you, sorry, I forgot".
I have suggested one above. There are surely others.
I have used the technique above to build a model of a chemical
plant. [Using dynamic linkage also] Here there are many
objects, some containing or transporting chemicals,
others are fences and bund walls.
There is a list of all objects.
There is a list of all objects of each type as well.
There is a list of all drawable objects.
Maintaining the lists is the 'run-time' overhead
required to remember the type: it happens once at
construction time.
Run time type check, on the other hand, is done
on all accesses. My method is much faster during operation,
yours faster at construction time.
>The real world is full of
>heterogeneous aggregates. But don't blame the real world just because C++ can
>not handle them,.
>
>> >For
>> >example, if you want to write an operation on a heterougeneous object
>> >stream, a window chains with different window types,...
>>
>> No, the programmer must not make such aggregates.
>> It is a misuse of inheritance.
>>
>
>I'm talking about run time type check, not the inheritance.
>I know what you try
>to say: a heterougeneous aggregate can not be specialized, right?
I dont understand.
>If you try to
>specialize a heterougeneous aggregate in the class hierarchy, you misuse
>inheritance. But it is not the point that relates to run time type checking.
>And besides, a sound language should prohibit specializing heterougeneous data
>structure. Unfortunately, C++ does not distinguish what is homogeneous and what
>is heterogeneous.
Yes it does. Arrays of objects are homogeneous.
Arrays of pointers must point to objects of the same base type.
>
>> The concept of 'thisclass' is useful. No argument!
>> But it does have disadvantages [As does power operator, no doubt]
>
>But you did not gave me any covincing argument on the disadvantage.
I am not trying to prove run-time type check is wrong, useless,
bad or unnecessary. But I *AM* saying that it is not correct to
use this facility for heterogeneous arrays (EVEN if ANSI gives
us that power)
It is up to you to find what I consider legitimate uses
if you want to convince me. I will think on it and post any that I think of.
>
>> And proper multiple dispatch would include it as a special case.
>>
>
>What is your proposal of a proper multiple dispatch?
Dont know. It would need run-time type information
perhaps.
>I cannot see anything that
>binds the two concepts together. If there is, it is the COMPLEMENT: "thisclass"
>is used to specify the component whose type is the same as the type of the
>enclosing object, while multiple dispatch requires the argument whose type is
>INDEPENDENT to the type of the enclosing object type.
>
"Thisclass" is but one of the possible combinations
multiple dispatch would support, the others too, with the user
controlling which were valid, what they meant, and which were
errors.
>
>David Shang
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: gjditchf@plg.waterloo.edu (Glen Ditchfield)
Date: Sun, 19 Jul 1992 19:26:57 GMT Raw View
In article <1992Jul15.141201.9307@math.waterloo.edu> gjditchf@plg.waterloo.edu
(Glen Ditchfield) writes:
> "thisclass" can be statically checked...
> Look at "Inheritance Is Not Subtyping" by Cook, Hill, and Canning, in the
> proceedings of the 1990 symposium on Principles of Programming Languages,
In article <1992Jul16.135956.8239@cadsun.corp.mot.com> shang@corp.mot.com writes:
>I disagree. Without run time type check, you must incur the same loophole in
>Eiffel.
The paper I mentioned above describes a type system that is statically
checked, has a "thisclass" equivalent, and does not have the Eiffel loophole.
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 10 Jul 92 17:29:14 GMT Raw View
Date: Tue, 7 Jul 92 22:24:57 PDT
From: rtm@grenada.island.COM (Richard Minner)
To: shang@corp.mot.com
Subject: Re: Speed, Correctness, Availability, and the Real World
Newsgroups: comp.lang.c++
In comp.lang.c++ you write:
>I would like to borrow the concept of "mytype" in POOL-I, or "ThisClass" in
>Beta, or "like current" in Eiffel, or "self new class" in SmallTalk. Though
the
>concept can help only in the case that the input or output type is the same as
>the type of the enclosing object, it is a complete solution in this case and
>has the practical value. The above example can written as follows:
> class Number
> { public:
> virtual thisclass operator+ (thisclass);
> };
>and in the derived class Float, the interface of "+" will be refined
>automatically to Float X Float -> Float.
Outstanding! I am so pleased that you posted this. (I have neither
the time nor the expertise to do it justice.) I couldn't agree
more -- the covariant output special case is a botch, a "thisclass"
approach would fill a large hole in the language. My example is
a "relational operators" base class, that takes a single "cmp()"
member function and provides == != >= <= < > from it. Can't be
done in today's C++. There are countless other similar situations
wherein a base class could provide some general service using
member functions defined in the derived classes. No such bases
can be provided currently.
I fear that your posting(s) may be too subtle for most readers.
You might come off as in the "Emperor Has No Clothes" camp.
But keep it up!
A quick note on syntax: how about "class this" instead of "thisclass" ?
Both 'class' and 'this' are reserved words and "class this" could
never legally appear in current C++. I think it reads fairly well.
class RelOps
{
public:
bool operator == (class this& other) const
{ return cmp(other) == 0; }
etc.
.
protected:
virtual int cmp(class this& other) const;
.
}
Anyway, I won't hold my breath for it. As I am fond of saying,
"C++ is only an incremental improvement over C." ;-)
--
Richard Minner rtm@island.com {uunet,sun,well}!island!rtm
Island Graphics Corporation Sacramento, CA (916) 736-1323
Author: maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller)
Date: Sat, 11 Jul 1992 06:19:58 GMT Raw View
In article <1992Jul10.172914.1257@cadsun.corp.mot.com> shang@corp.mot.com writes:
>Date: Tue, 7 Jul 92 22:24:57 PDT
>From: rtm@grenada.island.COM (Richard Minner)
>To: shang@corp.mot.com
>Subject: Re: Speed, Correctness, Availability, and the Real World
>Newsgroups: comp.lang.c++
>
>In comp.lang.c++ you write:
>
>>I would like to borrow the concept of "mytype" in POOL-I, or "ThisClass" in
>>Beta, or "like current" in Eiffel, or "self new class" in SmallTalk. Though
>the
>>concept can help only in the case that the input or output type is the same as
>>the type of the enclosing object, it is a complete solution in this case and
>>has the practical value. The above example can written as follows:
>
>> class Number
>> { public:
>> virtual thisclass operator+ (thisclass);
>> };
>
>>and in the derived class Float, the interface of "+" will be refined
>>automatically to Float X Float -> Float.
>
It is not so easy. For example, Eiffel does NOT have this
facility really. Instead, the 'thisclass' is just syntactic sugar,
and Meyer takes pains to point this out.
That would mean if you have
virtual int Base::cmp(Base&);
virtual int Derived::cmp(Derived&);
the derived cmp would NOT override the base one (they have differnt
signatures).
Ok, so why not write:
virtual int Derived::cmp(Base&);
which works with
d1.cmp(d2)
Well, it works with
d1.cmp(b1)
too and we don't want that. So what do you want in that case?
[The solution in other languages uses multiple dispatch]
>Outstanding! I am so pleased that you posted this. (I have neither
>the time nor the expertise to do it justice.) I couldn't agree
>more -- the covariant output special case is a botch, a "thisclass"
>approach would fill a large hole in the language. My example is
>a "relational operators" base class, that takes a single "cmp()"
>member function and provides == != >= <= < > from it. Can't be
>done in today's C++.
First thing *I* tried too.
As discussed in other posts, one possible solution
is to allow implicit generation of products (which
could then be redefined and derived from). Similarly
to the implicit generation of pointers.
[Note: in case I'm not being clear: cmp needs to depend on the
run time type of TWO arguments, not one. So if you have
n types, you need n*n methods for each combination. Some could
be defaulted: but HOW do you specify that? And HOW do
you do the lookup. Generalising to n-arguments you rapidly
find you need squillions of bytes ... just for the lookup
table!]
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: hf@informatik.uni-kl.de (Harald Fuchs)
Date: Sat, 11 Jul 1992 12:57:16 GMT Raw View
In article <1992Jul10.172914.1257@cadsun.corp.mot.com>, shang@corp.mot.com (David (Lujun) Shang) writes:
> Date: Tue, 7 Jul 92 22:24:57 PDT
> From: rtm@grenada.island.COM (Richard Minner)
> To: shang@corp.mot.com
> Subject: Re: Speed, Correctness, Availability, and the Real World
> Newsgroups: comp.lang.c++
> In comp.lang.c++ you write:
>> class Number
>> { public:
>> virtual thisclass operator+ (thisclass);
>> };
>>and in the derived class Float, the interface of "+" will be refined
>>automatically to Float X Float -> Float.
> Outstanding! I am so pleased that you posted this.
I'm less pleased. Allowing "thisclass" as an argument type for a
virtual function opens a new hole in the sieve called the C++ type
system. This hole (as present in Eiffel) can only be fixed by checking
type safety of the entire program rather than the classes in isolation.
(See e.g. Rick Jones in JOOP 5(2) for more about this.)
Contravariant argument types (like in Trellis) would be safe, but IMHO
of no practical use. Covariant return types (like in C++ since March '92)
are also safe and sometimes nice to have.
--
Harald Fuchs <hf@informatik.uni-kl.de>
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Tue, 14 Jul 92 00:20:43 GMT Raw View
In article <1992Jul10.172914.1257@cadsun.corp.mot.com>, shang@corp.mot.com
(David (Lujun) Shang) writes:
>
> >> class Number
> >> { public:
> >> virtual thisclass operator+ (thisclass);
> >> };
>
> >>and in the derived class Float, the interface of "+" will be refined
> >>automatically to Float X Float -> Float.
>
> > Outstanding! I am so pleased that you posted this.
In article <HF.92Jul11135716@demisec.informatik.uni-kl.de>
hf@informatik.uni-kl.de (Harald Fuchs) replies:
>
> I'm less pleased. Allowing "thisclass" as an argument type for a
> virtual function opens a new hole in the sieve called the C++ type
> system. This hole (as present in Eiffel) can only be fixed by checking
> type safety of the entire program rather than the classes in isolation.
> (See e.g. Rick Jones in JOOP 5(2) for more about this.)
> Contravariant argument types (like in Trellis) would be safe, but IMHO
> of no practical use. Covariant return types (like in C++ since March '92)
> are also safe and sometimes nice to have.
> --
Please, again, do not use Eiffel's hole against the concept of "thisclass". As
I mentioned in other posters in lang.c++, the concept of "like current" in
Eiffel is not a mature langauge facillity. And the hole of Eiffel is not just
caused by the concept of "like current", instead, it is caused by overriding
with direct covariant redefinition. The problem of "like current" concept in
Eiffel is due to the language -- it sticks to static type checking -- not due
to the concept itself.
> Contravariant argument types (like in Trellis) would be safe, but IMHO
> of no practical use. Covariant return types (like in C++ since March '92)
> are also safe and sometimes nice to have.
> --
I agree. And covariant input types and data member types are also be able to
safe and nice to have.
David Shang
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Tue, 14 Jul 92 00:22:15 GMT Raw View
In article <1992Jul10.172914.1257@cadsun.corp.mot.com> shang@corp.mot.com
writes:
I would like to borrow the concept of "mytype" in POOL-I, or "ThisClass" in
Beta, or "like current" in Eiffel, or "self new class" in SmallTalk. Though
the concept can help only in the case that the input or output type is the
same as the type of the enclosing object, it is a complete solution in this
case and has the practical value.
In article <1992Jul11.061958.21750@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John (MAX) Skaller) replies:
>
> It is not so easy. For example, Eiffel does NOT have this
> facility really. Instead, the 'thisclass' is just syntactic sugar,
> and Meyer takes pains to point this out.
>
> That would mean if you have
>
> virtual int Base::cmp(Base&);
> virtual int Derived::cmp(Derived&);
>
> the derived cmp would NOT override the base one (they have differnt
> signatures).
This is what you explain the semantics of thisclass in terms of current C++'s
overriding. In Eiffel,
virtual int Derived::cmp(Derived&); // not a real Eiffel code
DOES override
virtual int Base::cmp(Base&); // not a real Eiffel code
defined in Base.
What makes Meyer painful is not that "like current" is a syntatic sugar. The
real painful stuff is the overriding with redefinition. That is, let
virtual int Derived::cmp(Derived&);
override function
virtual int Base::cmp(Base&);
This is a theoretical loophole. Eiffel's redefinition rule is not a delicate
one. In fact, if such redefinition can be made indirectly through "thisclass",
there is no problem.
The concept of "like current" in Eiffel should not be just a syntactic sugar.
If you write cmp in Base:
virtual int Base::cmp(like current&);
instead of
virtual int Base::cmp(Base&);
the semantics is different. The formmer means that cmp should take an object of
the same type as the object that performs cmp. Unfortuately, Eiffel cannot
explore the concept further than a syntactic sugar, since the language sticks
to static type checking.
Languages that incorperate run time type check (e.g. Beta) will not have this
problem.
> Ok, so why not write:
>
> virtual int Derived::cmp(Base&);
>
> which works with
>
> d1.cmp(d2)
>
> Well, it works with
>
> d1.cmp(b1)
>
> too and we don't want that. So what do you want in that case?
>
I don't understand what you try to say here. The concept of "thisclass" has no
conflict here. If you want "di.cmp(b1)", do not use thisclass. If you do not
want "di.cmp(b1)" valid, please use thisclass.
rtm@grenada.island.COM (Richard Minner) write:
> >I couldn't agree more --
> >the covariant output special case is a botch, a "thisclass"
> >approach would fill a large hole in the language. My example is
> >a "relational operators" base class, that takes a single "cmp()"
> >member function and provides == != >= <= < > from it. Can't be
> >done in today's C++.
>
In article <1992Jul11.061958.21750@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John (MAX) Skaller) replies:
> First thing *I* tried too.
>
> As discussed in other posts, one possible solution
> is to allow implicit generation of products (which
> could then be redefined and derived from). Similarly
> to the implicit generation of pointers.
>
> [Note: in case I'm not being clear: cmp needs to depend on the
> run time type of TWO arguments, not one. So if you have
> n types, you need n*n methods for each combination. Some could
> be defaulted: but HOW do you specify that? And HOW do
> you do the lookup. Generalising to n-arguments you rapidly
> find you need squillions of bytes ... just for the lookup
> table!]
>
Here you make things complicated. It goes far from what "thisclass" try to
solve. If you want to impelment a cmp to compare two objects of any different
types, simple, just write cmp like:
bool cmp (anyclass &obj1, anyclass &obj2);
or,
bool anyclass::cmp (anyclass &obj);
-- if you just want to compare whether the machine code representation of these
two objects are equal. I cannot see any other reason that you should compare
two objects of any different types. For example, how do you compare a color
with an animal? At least, you should have a standard to establish the
magnitude. For example, use weight as a magnitude to measure all weighable
objects. Once the magnitude unit is established, it is not difficult to
implement the cmp operation.
class Magnitude
{ protected:
virtual unit_type unit ()
{ // convert this object to unit_type here };
public:
bool cmp ( Magnitude &obj )
{ return compare_unit (unit(), obj->unit(); };
};
David Shang
Author: maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller)
Date: Tue, 14 Jul 1992 11:51:01 GMT Raw View
In article <1992Jul14.002215.17725@cadsun.corp.mot.com> shang@corp.mot.com writes:
>
>In article <1992Jul10.172914.1257@cadsun.corp.mot.com> shang@corp.mot.com
>writes:
> I would like to borrow the concept of "mytype" in POOL-I, or "ThisClass" in
> Beta, or "like current" in Eiffel, or "self new class" in SmallTalk. Though
>
>In article <1992Jul11.061958.21750@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John (MAX) Skaller) replies:
>>
>> It is not so easy. For example, Eiffel does NOT have this
>> facility really. Instead, the 'thisclass' is just syntactic sugar,
>> and Meyer takes pains to point this out.
>>
> virtual int Derived::cmp(Derived&); // not a real Eiffel code
>
>DOES override
>
> virtual int Base::cmp(Base&); // not a real Eiffel code
>
>defined in Base.
>
>What makes Meyer painful is not that "like current" is a syntatic sugar. The
>real painful stuff is the overriding with redefinition. That is, let
I stand corrected.
>
> virtual int Derived::cmp(Derived&);
>
>override function
>
> virtual int Base::cmp(Base&);
>
>This is a theoretical loophole. Eiffel's redefinition rule is not a delicate
>one. In fact, if such redefinition can be made indirectly through "thisclass",
>there is no problem.
>
>The concept of "like current" in Eiffel should not be just a syntactic sugar.
>If you write cmp in Base:
>
> virtual int Base::cmp(like current&);
>
>instead of
>
> virtual int Base::cmp(Base&);
>
>the semantics is different. The formmer means that cmp should take an object of
>the same type as the object that performs cmp. Unfortuately, Eiffel cannot
>explore the concept further than a syntactic sugar, since the language sticks
>to static type checking.
As does C++ (at the moment)
>
>Languages that incorperate run time type check (e.g. Beta) will not have this
>problem.
Yes.
>
>> Ok, so why not write:
>>
>> virtual int Derived::cmp(Base&);
>>
>> which works with
>>
>> d1.cmp(d2)
>>
>> Well, it works with
>>
>> d1.cmp(b1)
>>
>> too and we don't want that. So what do you want in that case?
>>
>
>I don't understand what you try to say here.
>The concept of "thisclass" has no
>conflict here. If you want "di.cmp(b1)", do not use thisclass. If you do not
>want "di.cmp(b1)" valid, please use thisclass.
>
A proper 'thisclass' as you describe has three disadvantages:
1) It requires a run time type check. (And possible error)
2) It behave differently than other uses of references,
including just putting in the class name (since that
is not run-time checked)
3) It does not support proper multiple dispatch.
>(John (MAX) Skaller) replies:
>
>> First thing *I* tried too.
>>
>> As discussed in other posts, one possible solution
>> is to allow implicit generation of products (which
>> could then be redefined and derived from). Similarly
>> to the implicit generation of pointers.
>>
>> [Note: in case I'm not being clear: cmp needs to depend on the
>> run time type of TWO arguments, not one. So if you have
>> n types, you need n*n methods for each combination. Some could
>> be defaulted: but HOW do you specify that? And HOW do
>> you do the lookup. Generalising to n-arguments you rapidly
>> find you need squillions of bytes ... just for the lookup
>> table!]
>>
>
>Here you make things complicated. It goes far from what "thisclass" try to
>solve.
Yes. "thisclass" is of limited use, it is but a special
case of proper multiple dispatch. It carries many of
the disadvantages without the gain in semantic power.
>If you want to impelment a cmp to compare two objects of any different
>types, simple, just write cmp like:
>
> bool cmp (anyclass &obj1, anyclass &obj2);
>
>or,
>
> bool anyclass::cmp (anyclass &obj);
>
>-- if you just want to compare whether the machine code representation of these
>two objects are equal. I cannot see any other reason that you should compare
>two objects of any different types.
Of course not 'different' types. I want to compare objects
of class B and to include the ability to compare class D where
D is derived from B. Whether this makes sense I do not know,
perhaps it depends on context.
Certainly, I can *already* do this correctly if D is a true
subtype (subset) of B, the B::cmp already does this right thing.
If D is not a subtype, however (it might be a 'specialisation',
i.e. have extra information attached), there is NO automatic
way to do this, in particular because I need to *specify*
what such a comparison means.
>
>David Shang
>
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Tue, 14 Jul 92 19:25:25 GMT Raw View
In article <1992Jul14.115101.7134@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
(MAX) Skaller) writes:
>
> A proper 'thisclass' as you describe has three disadvantages:
>
> 1) It requires a run time type check. (And possible error)
Run time type check is not a disadvatage. The langauge must incorperate run
time type check mechanism, as I always insisted. There are two
misunderstandings about run time type check.
The first one is to view run time type check as an overhead. It is not. It is a
necessity. It is the common case that you have to run time check whether an
index is overflow the range of a array. And it is also a common case that you
have to run time check what the type of the object is in order to perform the
appropriate operations, especially for heterougeneous data structures. For
example, if you want to write an operation on a heterougeneous object stream, a
window chains with different window types, you have to know what is the actual
type of the current object. If the langauge does not incorperate run time type
check facillity, the programmer must have a sort of type id enclosed in the
object code and check the code at run time, for example, if
(window_p->type_id== text_window) .... This is surely inconvenient. The
programmer must always remember to assign a particular code to each different
type (I often made a type id mistake when I use C++ to write a window system)
and use type cast -- a dangerous feature if you forget to check the type id.
The second one is to view run time type check as a remedial measure to the
loopholes of a strongly typed system. My definition of strongly typed system
is: a strongly typed system should prevent any violation to the object
decaration at compile time. For example, if you declare a varible x that can
take any object of class B and of any derived class of B, then, the compiler
should prevent the variable x to take any value of the type which is not B or
B's derived. By this definition, C++ is not strongly typed (we do not consider
type cast, which is always a violation to the strongly typed principle). For
example:
Animal * ap;
Mammal mammal;
ap = &mammal;
*ap = reptile;
The varible mammal takes a value of reptile.
My definition of strongly-typed system does not exclude run time type check.
Since type can be a first class object, we can check and compare it at run
time. For example, if I want to define a function feed that feeds a animal of
any type with any food like this: feed it if it eats the food otherwise return
the food for some animal else. The function interface stipulates that it can
accept animal of any type and food of any type:
Food * feed (Animal *ap, Food *fp)
{ if (typeof(*ap)::FoodType==typeof(*fp))
{ ap->eat(*fp);
return null;
}
else return fp;
}
Therefore, feed (cat, hay) is a valid call (not a type error), and the run time
type check within the function body is not a remedial measure to the loopholes
of the function interface specification (the interface specification is
designed that way!).
> 2) It behave differently than other uses of references,
> including just putting in the class name (since that
> is not run-time checked)
Think about the self reference "this", is it a disadvantage of "this" that it
behaves differently than other uses of references? The semantics determines its
specific usage. This is not a disadvantage either. If it was, *MANY* things
would have a similiar disadvantadge, for example, the statement "exit" can only
be written within a loop.
> 3) It does not support proper multiple dispatch.
>
The concept of "thisclass" is not proposed for solving everything. It does not
support including power operator into C++ either. Again, this is not a
disadvantage. If it was, *EVERY* thing would have a similiar disadvantadge.
David Shang
Author: vinoski@apollo.hp.com (Stephen Vinoski)
Date: Tue, 14 Jul 1992 23:42:22 GMT Raw View
In article <1992Jul14.192525.24284@cadsun.corp.mot.com> you write:
>Run time type check is not a disadvatage. The langauge must
>incorperate run time type check mechanism, as I always insisted. There
>are two misunderstandings about run time type check.
>
>The first one is to view run time type check as an overhead. It is
>not. It is a necessity. It is the common case that you have to run
>time check whether an index is overflow the range of a array. And it
>is also a common case that you have to run time check what the type of
>the object is in order to perform the appropriate operations,
>especially for heterougeneous data structures. For example, if you
>want to write an operation on a heterougeneous object stream, a window
>chains with different window types, you have to know what is the
>actual type of the current object. If the langauge does not
>incorperate run time type check facillity, the programmer must have a
>sort of type id enclosed in the object code and check the code at run
>time, for example, if (window_p->type_id== text_window) .... This is
>surely inconvenient. The programmer must always remember to assign a
>particular code to each different type (I often made a type id mistake
>when I use C++ to write a window system) and use type cast -- a
>dangerous feature if you forget to check the type id.
Sorry to bring up this rat-hole again, but I (and others) have a
problem with the concept of "heterogeneous object streams" and
heterogeneous data structures in C++. What good are they in a
language like C++, where, if I maintain the type of an object, the
compiler will ensure its safe and proper treatment? If you require
the actual type of an object, don't lose it in the first place. In my
opinion, a C++ program using run-time type checking usually (not
always) indicates an error in design. I believe that your "window
chain" is an example of such a case.
>The second one is to view run time type check as a remedial measure to
>the loopholes of a strongly typed system. My definition of strongly
>typed system is: a strongly typed system should prevent any violation
>to the object decaration at compile time. For example, if you declare
>a varible x that can take any object of class B and of any derived
>class of B, then, the compiler should prevent the variable x to take
>any value of the type which is not B or B's derived. By this
>definition, C++ is not strongly typed (we do not consider type cast,
>which is always a violation to the strongly typed principle). For
>example:
>
> Animal * ap;
> Mammal mammal;
> ap = &mammal;
> *ap = reptile;
>
>The varible mammal takes a value of reptile.
Actually, no. Here, `*a' and `reptile' both are "sliced" down to
their base Animal type (ARM page 298), so that the Animal part of
`reptile' is assigned to the Animal part of `mammal'. Assignment of
Animal to Animal makes perfect sense, but in this situation it appears
to be of little practical value. Programmers writing such code would
either have good reason to make this assignment, or they don't
understand the language and shouldn't be using it.
This "problem" is well documented in the ARM - why bring it up now
and treat it like some kind of novel finding?
>My definition of strongly-typed system does not exclude run time type
>check. Since type can be a first class object, we can check and
>compare it at run time. For example, if I want to define a function
>feed that feeds a animal of any type with any food like this: feed it
>if it eats the food otherwise return the food for some animal else.
>The function interface stipulates that it can accept animal of any
>type and food of any type:
>
> Food * feed (Animal *ap, Food *fp)
> { if (typeof(*ap)::FoodType==typeof(*fp))
> { ap->eat(*fp);
> return null;
> }
> else return fp;
> }
>
>Therefore, feed (cat, hay) is a valid call (not a type error), and the
>run time type check within the function body is not a remedial measure
>to the loopholes of the function interface specification (the
>interface specification is designed that way!).
No run-time type check needed here - this interface is poorly
designed. We all know that any old animal can't eat any old food; why
design an interface with those characteristics?
Run-time type checking is required by the C++ language environment so
that exceptions can be properly handled. For example:
try {
// some operation ...
} catch (derived &d) {
// handle derived ...
} catch (base &b) {
// handle base ...
}
Here, run-time type checking is required to ensure that the "derived"
type is handled separately from the "base" type from which it is
derived.
The proposal to add run-time type checking to C++ was made simply
because the mechanism had to be there for exceptions anyway and it
made sense to expose the feature to programmers. I believe the
proposal was made by Dmitri Lenkov of HP, and I'm sure he did not mean
it to be used to cover up poor design and programming practices.
-steve
--
Steve Vinoski (508)436-5904 vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824 These are my opinions.
Author: maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller)
Date: Wed, 15 Jul 1992 13:03:07 GMT Raw View
In article <1992Jul14.192525.24284@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul14.115101.7134@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>(MAX) Skaller) writes:
>>
>> A proper 'thisclass' as you describe has three disadvantages:
>>
>> 1) It requires a run time type check. (And possible error)
>
>Run time type check is not a disadvatage.
OK, rephrase: having a run-time check where not
necessary is a disadvantage.
>The langauge must incorperate run
>time type check mechanism, as I always insisted.
It doesn't *have* to (obviously).
>There are two
>misunderstandings about run time type check.
>
>The first one is to view run time type check as an overhead. It is not. It is a
>necessity.
I would say this backwards: IF it is necessary,
THEN you must incur the overhead.
>It is the common case that you have to run time check whether an
>index is overflow the range of a array.
On the contrary, in C++ it is the case that you CANT do
a run time type check on an array! (At least
not automatically using built in arrays)
>And it is also a common case that you
>have to run time check what the type of the object is in order to perform the
>appropriate operations,
It is not common in my programs. I have never had to do
this.
>especially for heterougeneous data structures.
Yes, so if your design is base on heterogeneous aggregates,
it is probably faulty. C++ can't handle them, and I argue MOST of the time,
they are not required.
>For
>example, if you want to write an operation on a heterougeneous object stream, a
>window chains with different window types, you have to know what is the actual
>type of the current object. If the langauge does not incorperate run time type
>check facillity, the programmer must have a sort of type id enclosed in the
>object code and check the code at run time
No, the programmer must not make such aggregates.
It is a misuse of inheritance.
>, for example, if
>(window_p->type_id== text_window) .... This is surely inconvenient. The
>programmer must always remember to assign a particular code to each different
>type (I often made a type id mistake when I use C++ to write a window system)
>and use type cast -- a dangerous feature if you forget to check the type id.
It won't even WORK if the base is virtual.
It defeats the open/closed principle, invalidating
the use of inheritance. If you have to do this switching of
casting to specific, known in advance types, you should
not be using inheritance.
>
>The second one is to view run time type check as a remedial measure to the
>loopholes of a strongly typed system. My definition of strongly typed system
>is: a strongly typed system should prevent any violation to the object
>decaration at compile time. For example, if you declare a varible x that can
>take any object of class B and of any derived class of B, then, the compiler
>should prevent the variable x to take any value of the type which is not B or
>B's derived. By this definition, C++ is not strongly typed (we do not consider
>type cast, which is always a violation to the strongly typed principle).
Sorry, I dont agree, by your definition C++ IS strongly typed.
>For
>example:
>
> Animal * ap;
> Mammal mammal;
> ap = &mammal;
> *ap = reptile;
[I assume mammal and reptile are derived from animal]
>
>The varible mammal takes a value of reptile.
NO IT DOES NOT. The 'animal' part ONLY is copied,
NOT the reptile part.
There are two cases.
1) You have non-virtual assignment.
In that case, you are calling
Animal::operator=(Animal&); // copy animal part
2) You have virtual assignment.
In that case you are calling
Mammal::operator=(Animal&) // STILL copies animal part
SO in NEITHER case can you copy the reptile part.
3) The case
Mammal::operator=(Mammal&)
CANNOT be called here, because C++ is strongly typed!
>
>My definition of strongly-typed system does not exclude run time type check.
Agreed.
>Since type can be a first class object, we can check and compare it at run
>time. For example, if I want to define a function feed that feeds a animal of
>any type with any food like this: feed it if it eats the food otherwise return
>the food for some animal else. The function interface stipulates that it can
>accept animal of any type and food of any type:
>
> Food * feed (Animal *ap, Food *fp)
> { if (typeof(*ap)::FoodType==typeof(*fp))
> { ap->eat(*fp);
> return null;
> }
> else return fp;
> }
>
>Therefore, feed (cat, hay) is a valid call (not a type error), and the run time
>type check within the function body is not a remedial measure to the loopholes
>of the function interface specification (the interface specification is
>designed that way!).
This case requires run-time operation: it requires
multiple dispatch I think. But in the example as you have encoded
it the run time check is inserted by YOU. Not the compiler.
>
>> 2) It behave differently than other uses of references,
>> including just putting in the class name (since that
>> is not run-time checked)
>
>Think about the self reference "this", is it a disadvantage of "this" that it
>behaves differently than other uses of references?
It doesn't. (Well, actually it is a pointer).
The problem is that you can only dispatch on the object.
Really, we want to dispatch on ALL the arguments.
Using 'thisclass' is a VERY restricted form of this.
[But useful, I agree! I am just trying to point out
the disadvantages]
>The semantics determines its
>specific usage. This is not a disadvantage either. If it was, *MANY* things
>would have a similiar disadvantadge, for example, the statement "exit" can only
>be written within a loop.
>
>> 3) It does not support proper multiple dispatch.
>>
>
>The concept of "thisclass" is not proposed for solving everything. It does not
>support including power operator into C++ either. Again, this is not a
>disadvantage. If it was, *EVERY* thing would have a similiar disadvantadge.
>
The concept of 'thisclass' is useful. No argument!
But it does have disadvantages [As does power operator, no doubt]
And proper multiple dispatch would include it as a special case.
>
>David Shang
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: gjditchf@plg.waterloo.edu (Glen Ditchfield)
Date: Wed, 15 Jul 1992 14:12:01 GMT Raw View
In article <1992Jul14.002215.17725@cadsun.corp.mot.com> shang@corp.mot.com writes:
>... Unfortuately, Eiffel cannot explore the concept further than a
>syntactic sugar, since the language sticks to static type checking.
In article <1992Jul14.115101.7134@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller) writes:
> A proper 'thisclass' as you describe has three disadvantages:
> 1) It requires a run time type check. (And possible error)
It doesn't have to be like that. "thisclass" can be statically checked.
Look at "Inheritance Is Not Subtyping" by Cook, Hill, and Canning, in the
proceedings of the 1990 symposium on Principles of Programming Languages,
and don't let the lambdas scare you away. In that paper the name of the
current "interface" is the equivalent of "thisclass".
C++ won't adopt their type system because the changes needed are too
deep. For a start, descendant classes are not necessarily subtypes of
their ancestors.
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 15 Jul 92 14:40:12 GMT Raw View
> In article <1992Jul14.192525.24284@cadsun.corp.mot.com> I write:
> >Run time type check is not a disadvatage. The langauge must
> >incorperate run time type check mechanism, as I always insisted. There
> >are two misunderstandings about run time type check.
> >
> >The first one is to view run time type check as an overhead. It is
> >not. It is a necessity. It is the common case that you have to run
> >time check whether an index is overflow the range of a array. And it
> >is also a common case that you have to run time check what the type of
> >the object is in order to perform the appropriate operations,
> >especially for heterougeneous data structures. For example, if you
> >want to write an operation on a heterougeneous object stream, a window
> >chains with different window types, you have to know what is the
> >actual type of the current object. If the langauge does not
> >incorperate run time type check facillity, the programmer must have a
> >sort of type id enclosed in the object code and check the code at run
> >time, for example, if (window_p->type_id== text_window) .... This is
> >surely inconvenient. The programmer must always remember to assign a
> >particular code to each different type (I often made a type id mistake
> >when I use C++ to write a window system) and use type cast -- a
> >dangerous feature if you forget to check the type id.
In article <BrEKIM.Ln9@apollo.hp.com> vinoski@apollo.hp.com (Stephen Vinoski)
replies:
>
> Sorry to bring up this rat-hole again, but I (and others) have a
> problem with the concept of "heterogeneous object streams" and
> heterogeneous data structures in C++. What good are they in a
> language like C++, where, if I maintain the type of an object, the
> compiler will ensure its safe and proper treatment? If you require
> the actual type of an object, don't lose it in the first place. In my
> opinion, a C++ program using run-time type checking usually (not
> always) indicates an error in design.
Run time type check is not necessarily to check the error, that's the keypoint
in my opinion. For example:
void Do (HeterIOStream * str)
{ IO_Object * obj
while (!str->end())
{ obj = str->get_next();
switch (typeof(*obj))
{ case char: char ch =*obj; ...// do something on ch
case int: int i =*obj; ... // do something on i
case float: float f =*obj ...// do something on f
...
}
}
}
> I believe that your "window
> chain" is an example of such a case.
>
Yes, this is another good example. Heterogeneous data structure is a very
common data structure. Run time type checking of a element of a heterogeneous
data container is a necessity. If you say that run-time type checking usually
indicates an error in design, you should add a premise: for homogeneous case.
I write:
> >The second one is to view run time type check as a remedial measure to
> >the loopholes of a strongly typed system. My definition of strongly
> >typed system is: a strongly typed system should prevent any violation
> >to the object decaration at compile time. For example, if you declare
> >a varible x that can take any object of class B and of any derived
> >class of B, then, the compiler should prevent the variable x to take
> >any value of the type which is not B or B's derived. By this
> >definition, C++ is not strongly typed (we do not consider type cast,
> >which is always a violation to the strongly typed principle). For
> >example:
> >
> > Animal * ap;
> > Mammal mammal;
> > ap = &mammal;
> > *ap = reptile;
> >
> >The varible mammal takes a value of reptile.
>
Stephen Vinoski replies:
> Actually, no. Here, `*a' and `reptile' both are "sliced" down to
> their base Animal type (ARM page 298), so that the Animal part of
> `reptile' is assigned to the Animal part of `mammal'.
So, why
mammal = reptile;
is prohibited? We can make the assignment valid by assigning the Animal part of
`reptile' to the Animal part of `mammal'. To generalize this case, we can
assign object or pointer of any type to a varible or pointer of any other type
since they can have a common root base.
We should keep in mind that the state of the common part of two different types
may be different. A abstract data type also encapsulates a set of operations on
the data structure. Type of Reptile and type of Mammal may manipulates the
Animal part in different way. Therefore, to assign the Animal part of
`reptile' to the Animal part of `mammal' may produce an inconsistent object: a
mammal with reptile head!
> Assignment of
> Animal to Animal makes perfect sense, but in this situation it appears
> to be of little practical value. Programmers writing such code would
> either have good reason to make this assignment, or they don't
> understand the language and shouldn't be using it.
>
The problem is that programmers are not aware of that. This assignment is made
by an accident. If they have god reason to make this assignment, they should
use explicit type cast. Otherwise, the language should prohibit any error-prone
assignement. I recommend using run time type check:
if (typeof(*ap)==mammal) *ap = anotherMammal;
> This "problem" is well documented in the ARM - why bring it up now
> and treat it like some kind of novel finding?
>
I'm not talking about the problem itself. I'm talking about the solution to
this problem.
I write:
> >My definition of strongly-typed system does not exclude run time type
> >check. Since type can be a first class object, we can check and
> >compare it at run time. For example, if I want to define a function
> >feed that feeds a animal of any type with any food like this: feed it
> >if it eats the food otherwise return the food for some animal else.
> >The function interface stipulates that it can accept animal of any
> >type and food of any type:
> >
> > Food * feed (Animal *ap, Food *fp)
> > { if (typeof(*ap)::FoodType==typeof(*fp))
> > { ap->eat(*fp);
> > return null;
> > }
> > else return fp;
> > }
> >
> >Therefore, feed (cat, hay) is a valid call (not a type error), and the
> >run time type check within the function body is not a remedial measure
> >to the loopholes of the function interface specification (the
> >interface specification is designed that way!).
Stephen Vinoski replies:
>
> No run-time type check needed here - this interface is poorly
> designed. We all know that any old animal can't eat any old food; why
> design an interface with those characteristics?
>
Could you give me a type safe interface for "feed" in C++ that prohibits any
wrong combination of animal and food? No way in C++. That's the problem we need
to solve, not only in C++, but also in other OO languages. Think about a
solution before saying that the interface is poorly designed.
> Run-time type checking is required by the C++ language environment so
> that exceptions can be properly handled. For example:
>
> try {
> // some operation ...
> } catch (derived &d) {
> // handle derived ...
> } catch (base &b) {
> // handle base ...
> }
>
> Here, run-time type checking is required to ensure that the "derived"
> type is handled separately from the "base" type from which it is
> derived.
>
Again, run time type check is not necessarily always to treat type errors!
> The proposal to add run-time type checking to C++ was made simply
> because the mechanism had to be there for exceptions anyway and it
> made sense to expose the feature to programmers. I believe the
> proposal was made by Dmitri Lenkov of HP, and I'm sure he did not mean
> it to be used to cover up poor design and programming practices.
>
I feel sorry about that.
David Shang
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Thu, 16 Jul 92 13:59:56 GMT Raw View
In article <1992Jul15.141201.9307@math.waterloo.edu> gjditchf@plg.waterloo.edu
(Glen Ditchfield) writes:
> "thisclass" can be statically checked.
I disagree. Without run time type check, you must incur the same loophole in
Eiffel.
> Look at "Inheritance Is Not Subtyping" by Cook, Hill, and Canning, in the
> proceedings of the 1990 symposium on Principles of Programming Languages,
> and don't let the lambdas scare you away. In that paper the name of the
> current "interface" is the equivalent of "thisclass".
No doubt the argument illustrated by this paper is correct: class hierarchy and
subtyping are theoretically two different things. But don't be misled by the
points in this paper. In general, it is not necessary to have two different
systems in the same language (One exception: making SmallTalk typed, you have
to introduce a new type system because an argument in SmallTalk may represent
anything).
The covariant problem is not due to the intrinsic defects of class hierachy. If
you get the conclusion that we must introduce subtyping lattice to solve the
problem, that's wrong. The problem is due to the type-intra dependency which
has not been fully recognized so far (see "Type-Safe Reuse of Prototype
Software" in Proceedings of 3rd Intl. Conf. on Software Eng. and Knowledge Eng.
Skokie, Illinois, p230). "thisclass" is a partial solution because it descibes
a specific type-intra dependency: the type of the component is dependent as a
whole on its enclosing object.
> C++ won't adopt their type system because the changes needed are too
> deep. For a start, descendant classes are not necessarily subtypes of
> their ancestors.
C++ won't adopt. Other typed languages like Eiffel, Trellis/Owl won't adopt it
either. Why? Because you must first make these langauges untyped, and then wrap
them with their type system. If a class-based language is originally untyped,
to wrap them with the type system is a good practice, which has been exercised
in several typed smalltalk systems.
David Shang
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 15 Jul 92 19:26:28 GMT Raw View
> In article <1992Jul14.192525.24284@cadsun.corp.mot.com> shang@corp.mot.com
writes:
>
> >The first one is to view run time type check as an overhead. It is not. It
> >is a necessity.
In article <1992Jul15.130307.28112@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John (MAX) Skaller) writes:
>
> I would say this backwards: IF it is necessary,
> THEN you must incur the overhead.
>
When I say it is necessary, I don't mean it is always necessary. But as a
general purposed language, it should include a facility when it is necessary in
some *common* case.
> >It is the common case that you have to run time check whether an
> >index is overflow the range of a array.
>
> On the contrary, in C++ it is the case that you CANT do
> a run time type check on an array! (At least
> not automatically using built in arrays)
>
What C++ can't do is not what the real world should not have. Besides, I am not
talking about automatic run time index check.
> >And it is also a common case that you
> >have to run time check what the type of the object is in order to perform
> >the appropriate operations,
>
> It is not common in my programs. I have never had to do
> this.
>
Be realitic, please. What you have not is not what others have not either.
> >especially for heterougeneous data structures.
>
> Yes, so if your design is base on heterogeneous aggregates,
> it is probably faulty. C++ can't handle them, and I argue MOST of the time,
> they are not required.
>
Now, you see what you have not seen before. The real world is full of
heterogeneous aggregates. But don't blame the real world just because C++ can
not handle them,.
> >For
> >example, if you want to write an operation on a heterougeneous object
> >stream, a window chains with different window types,...
>
> No, the programmer must not make such aggregates.
> It is a misuse of inheritance.
>
I'm talking about run time type check, not the inheritance. I know what you try
to say: a heterougeneous aggregate can not be specialized, right? If you try to
specialize a heterougeneous aggregate in the class hierarchy, you misuse
inheritance. But it is not the point that relates to run time type checking.
And besides, a sound language should prohibit specializing heterougeneous data
structure. Unfortunately, C++ does not distinguish what is homogeneous and what
is heterogeneous.
> > Animal * ap;
> > Mammal mammal;
> > ap = &mammal;
> > *ap = reptile;
> > The varible mammal takes a value of reptile.
>
> NO IT DOES NOT. The 'animal' part ONLY is copied,
> NOT the reptile part.
>
I won't repeat my statement, see my previous poster.
> >For example, if I want to define a function feed that feeds a animal of
> >any type with any food like this: feed it if it eats the food otherwise
> >return the food for some animal else. The function interface stipulates that
> >it can accept animal of any type and food of any type ...
>
> This case requires run-time operation: it requires
> multiple dispatch I think. But in the example as you have encoded
> it the run time check is inserted by YOU. Not the compiler.
>
Yes, that's what I want: to check and compare types just as it is a first class
object. You can check and compare types WHEN necessary. No overhead.
> >
> >Think about the self reference "this", is it a disadvantage of "this" that
> >it behaves differently than other uses of references?
>
> It doesn't. (Well, actually it is a pointer).
>
> The problem is that you can only dispatch on the object.
>
> Really, we want to dispatch on ALL the arguments.
>
> Using 'thisclass' is a VERY restricted form of this.
> [But useful, I agree! I am just trying to point out
> the disadvantages]
>
You always talk about "dispacth on all" or "multiple dispatch". On the
contrary, what "thisclass" tries to do is to allow covariant input type in
derived classes, which solves problems completely different from what multiple
dispatch try to solve. (As talking about multiple dispatch, I have to say it
again: run time type check is necessary for dealing with multiple dispatch)
> The concept of 'thisclass' is useful. No argument!
> But it does have disadvantages [As does power operator, no doubt]
But you did not gave me any covincing argument on the disadvantage.
> And proper multiple dispatch would include it as a special case.
>
What is your proposal of a proper multiple dispatch? I cannot see anything that
binds the two concepts together. If there is, it is the COMPLEMENT: "thisclass"
is used to specify the component whose type is the same as the type of the
enclosing object, while multiple dispatch requires the argument whose type is
INDEPENDENT to the type of the enclosing object type.
David Shang
Author: jrobie@netmbx.netmbx.de (Jonathan Robie)
Date: Thu, 16 Jul 1992 07:51:50 GMT Raw View
>> a C++ program using run-time type checking usually (not
>> always) indicates an error in design.
If it is a strictly normal application program, this may be true.
Many classes of programs simply need run-time type information:
debuggers
object oriented databases
object oriented communications packages
class browsers which derive info from the executable (like
Borland's)
If run-time type info were available then it would be possible to easily
port your favorite debugger and class browser for use with your favorite
compiler, or to use one debugger with the 9 compilers you support for
your product.
Jonathan
===========================================================================
Jonathan Robie jrobie@netmbx.UUCP
Arnold-Zweig-Str. 44 jrobie@netmbx.in-berlin.de
O-1100 Berlin
Deutschland Phone: +37 (2) 472 04 19 (Home, East Berlin)
+49 (30) 342 30 66 (Work, West Berlin)
--
Jonathan
===========================================================================
Author: hendrik@vedge.UUCP (Hendrik Boom)
Date: 15 Jul 92 15:37:05 GMT Raw View
shang@corp.mot.com (David (Lujun) Shang) writes:
: In article <1992Jul10.172914.1257@cadsun.corp.mot.com>, shang@corp.mot.com
: (David (Lujun) Shang) writes:
: In article <HF.92Jul11135716@demisec.informatik.uni-kl.de>
: hf@informatik.uni-kl.de (Harald Fuchs) replies:
: > of no practical use. Covariant return types (like in C++ since March '92)
It looks as if something happened on March '92 that I should know about.
A new version was implemented? A draft standard appeared?
Please inform.
--
-------------------------------------------------------
Try one or more of the following addresses to reply.
at work: hendrik@vedge.com, iros1!vedge!hendrik
at home: uunet!altitude!ozrout!topoi!hendrik
Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Thu, 16 Jul 92 23:50:58 GMT Raw View
In article <1992Jul16.124016.5775@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
(MAX) Skaller) writes:
> It is not that run-time type checking is
> only for error check. It is that any DESIGN you make requiring
> run-time type knowledge is itself *in error*. In particular,
> any design based on heterogenous aggregates where type knowledge
> is required of specific types is flawed. It breaks the
> open/closed principle.
>
I strongly disagree! By your definition, the design of OO databases, OO
operating systems, the OO user interfaces, and many many other OO systems are
flawed as a whole, because they do require rum-time type knowledge. The whole
world is flawed because it is full of heterogeneous structure. Your open/close
principle is not the panacea.
> >
> > void Do (HeterIOStream * str)
> > { IO_Object * obj
> > while (!str->end())
> > { obj = str->get_next();
> > switch (typeof(*obj))
> > { case char: char ch =*obj; ...// do something on ch
> > case int: int i =*obj; ... // do something on i
> > case float: float f =*obj ...// do something on f
> > ...
>
> Look: you have written a SWITCH on the run-time type.
> That is ALWAYS ( :-) an error in design. Why? Because it implies
> that you know ALL the types in advance. But the use
> of types derived from a common base IMPLIES that you do not.
>
> IF you know all the types in advance, the switch
> should be taken BEFORE the object is constructed, then
> the appropriate routine knows what to do with it
If you are still not willing to adimit that the world is full of heterogeneous
structure, I have nothing to say here.
> >Heterogeneous data structure is a very
> >common data structure.
>
> For people with Smalltalk backgrounds. Where it is
> correct technique. In C++ it is not. It is common *mistake*
> in C++.
>
Yes, because C++ does not support it properly. But remember, C++ is only a
programming language! Do not forget the responsibility of a langauge: it is
used to model the real world. It is not the standard to tell that what the real
world should be.
> >Run time type checking of a element of a heterogeneous
> >data container is a necessity.
>
> No, it is never necessary. Why: the proof is a
> proof 'in vacuo', namely, heterogeneous data containers
> are never necessary.
>
> It is true that sometimes when you think you need one
> it seems simpler. It is simpler. It is simpler to write
> C programs than get a good C++ class done too. Proper
> design is hard, in C++ you pay severe penalties for incorrect
> design. It is one of the disadvantages of C++, it is not
> very good for 'rapid prototyping'.
>
Oh, let C++ guide your life. It must be a very very easy life. Every thing will
be arranged in advance, and you know all the arrangements. Tell me, please, ten
years later, the same day and the same time, what are you doing?
Heterogeneous programming is not simpler than homogeneous programming. What is
not simple is that you try to use homogeneous data structure to describe the
heterougeneous world!
> >
> >So, why
> >
> > mammal = reptile;
> >
> >is prohibited?
>
> It is not prohibited.
>
It is prohibited by the built-in operator=. It is meanless to talk about the
user overloaded operator.
> >We should keep in mind that the state of the common part of two different
> >types may be different.
>
> Your argument is faulty but only because it does not go far enough.
>
> Consider:
>
> animal *a=new mammal( ... );
> animal *b=new mammal( ... );
> *a = *b; // slicing of 'animal part'
>
> Now here you see the very same slicing problem. The animal part
> of 'a' may be intimately tied to the mammal part, and assigning
> the animal part of 'b' to it may destroy the integrity
> of the 'a' object.
>
Yes, it does in C++. But it does not in a langauge that uses type inference
technique: *a=*b should be an entire assignment, no slicing should happen in
your above codes.
>
> See above. Assigning a mammal to a mammal through a reference
> to animal might produce a cow with a horses head :-)
>
Again, this is the problem with C++.
> >I recommend using run time type check:
> >
> > if (typeof(*ap)==mammal) *ap = anotherMammal;
>
> Doesn't help, it is not a problem related to incompatible
> derived types, but to only having single dispatch on the assignment
> operator.
> >
It helps. You don't think it is a solution because you are still thinking in
C++. Again, no slicing here, *ap=anotherMammal is an entire assignment.
> >
> >I'm not talking about the problem itself. I'm talking about the solution to
> >this problem.
> >
>
> But as you see, you do not solve the problem.
> >
Now, can you see the solution?
> > Food * feed (Animal *ap, Food *fp)
> > { ap->eat(*fp); }
> >Could you give me a type safe interface for "feed" in C++ that prohibits any
> >wrong combination of animal and food? No way in C++.
>
> Of course I can give it to you.
>
> class Fly {};
> class Meat {};
> class Lizard { eat(Fly f); };
> class Cat { eat(Meat m); };
>
You did not give me what I want. Where is your definition of "feed"?
> >That's the problem we need
> >to solve, not only in C++, but also in other OO languages. Think about a
> >solution before saying that the interface is poorly designed.
>
> [There are many other solutions, depending on what your specification
> is.]
>
Hey, you did not even give a sigle solution, but you claim that there are many
other solutions.
David Shang