Topic: Who Win the War of Co. vs. Contra?
Author: shang@corp.mot.com (David L. Shang)
Date: Thu, 31 Mar 1994 21:17:54 GMT Raw View
In article <2nek7q$20b@hpsystem1.informatik.tu-muenchen.de>
berger@Informatik.TU-Muenchen.DE (Christoph Berger) writes:
> If we assume a homogeneous matrix, your cluster class can be easily written
> as a C++ class, too. No xyz-variance is needed at all (if I interpret your
> definition of homogeneous right).
The fundamental difference between C++ template and Cluster class on this
Matrix stuff includes:
1. C++ template is not a real class while Cluster class is. Therefore, in
Cluster, you can have a polymorphic interface on Matrix, but you cannot have
that interface on template. With Cluster's Matrix class, you can define a
function interface like:
function (Matrix: m);
That is why Cluster also allows you to have a "mult" method with heterogeneous
matrix input.
2. Cluster's Matrix (sub)class can be created at run time and accept run time
value to construct the new subclass(dynamic typing), while C++ template cannot.
For example:
function ReadMatrixSpec (File: specfile): Matrix
var line, col: cardinal;
element: Number;
matrix: Matrix;
begin
...
line := GetIntegerFromAMatrixSpecFile (specfile);
col := GetIntegerFromAMatrixSpecFile (specfile);
element := GetNumberFromAMatrixSpecFile (specfile);
matrix := new Matrix[typeof(element),line,col];
...
return matrix;
end;
3. Cluster's Matrix class is guaranteed type-safe at the time of design but C++
template is guaranteed type-safe only at the time of using it. C++ template is
not a good feature from the viewpoint of software engineering.
4. Cluster's Matrix code is shared while C++ template code is a source for
duplication. This is not a big issue. but sometimes can save a lots of memory
space.
David Shang
Author: g2devi@cdf.toronto.edu (Robert N. Deviasse)
Date: Fri, 1 Apr 1994 15:08:48 GMT Raw View
In article <1994Mar31.211754.29838@schbbs.mot.com> shang@corp.mot.com writes:
>In article <2nek7q$20b@hpsystem1.informatik.tu-muenchen.de>
>berger@Informatik.TU-Muenchen.DE (Christoph Berger) writes:
>> If we assume a homogeneous matrix, your cluster class can be easily written
>> as a C++ class, too. No xyz-variance is needed at all (if I interpret your
>> definition of homogeneous right).
>
>The fundamental difference between C++ template and Cluster class on this
>Matrix stuff includes:
>
>1. C++ template is not a real class while Cluster class is. Therefore, in
>Cluster, you can have a polymorphic interface on Matrix, but you cannot have
>that interface on template. With Cluster's Matrix class, you can define a
>function interface like:
>
> function (Matrix: m);
>
>That is why Cluster also allows you to have a "mult" method with heterogeneous
>matrix input.
>
>2. Cluster's Matrix (sub)class can be created at run time and accept run time
>value to construct the new subclass(dynamic typing), while C++ template cannot.
>For example:
>
> function ReadMatrixSpec (File: specfile): Matrix
> var line, col: cardinal;
> element: Number;
> matrix: Matrix;
> begin
> ...
> line := GetIntegerFromAMatrixSpecFile (specfile);
> col := GetIntegerFromAMatrixSpecFile (specfile);
> element := GetNumberFromAMatrixSpecFile (specfile);
> matrix := new Matrix[typeof(element),line,col];
> ...
> return matrix;
> end;
>
>3. Cluster's Matrix class is guaranteed type-safe at the time of design but C++
>template is guaranteed type-safe only at the time of using it. C++ template is
>not a good feature from the viewpoint of software engineering.
>
>4. Cluster's Matrix code is shared while C++ template code is a source for
>duplication. This is not a big issue. but sometimes can save a lots of memory
>space.
>
>David Shang
Yes, but all these this only show one thing, Cluster and C++ address different
problem domains. I don't use C++ to do intensive A.I. (at least not willingly
:-[) or real-time animation in CLOS (at least on a P.C.:-]). C++ is, from your
description, a far lower level language and I honestly don't see a way
Cluster-like features can be added to it. And if you could, can you imagine
the complexity of such a language?!?! Would you use or even be able to
understand all that was going on? How on earth would you debug the beast???
There will always be languages that are better expressing certain problems than
others. We haven't done any better with natural languages and all their jargon
so what makes you believe we can do any better with artificial languages?
It all comes down to one principle:
"Know your problem. Then choose your tools."
Take care
Robert
--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse |"If we have to re-invent the wheel, |
| EMAIL: g2devi@cdf.utoronto.ca | can we at least make it round this time"|
+----------------------------------+------------------------------------------/
Author: berger@Informatik.TU-Muenchen.DE (Christoph Berger)
Date: 5 Apr 1994 12:23:04 GMT Raw View
In article <1994Mar31.211754.29838@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
|>
|> The fundamental difference between C++ template and Cluster class on this
|> Matrix stuff includes:
I think we moved a little bit off topic. We started discussing the aspects
of covariance, contravariance and no-variance and I don't want it to end up
in a detailed comparison of every language feature that that C++ and Cluster
have or have not in common. Rather, I'd like to see a language description of
Cluster to make my own thoughts about it. Are there any documents available
via ftp or WWW?
--
| Christoph Berger berger@informatik.tu-muenchen.de |
| c.berger@ieee.org |
| Christoph_Berger@m.maus.de |
Author: amiel@rita.inria.fr (Eric Amiel INRIA ROCQUENCOURT)
Date: 5 Apr 1994 12:57:15 GMT Raw View
At this point of the discussion, I would like to make the following point :
When you have covariant arguments, no matter how sophisticated your type
system/ type checker is, there are always simple cases that are beyond the
reach of static type checking.
For example :
Animal a;
Food f;
if condition1 then
a = new Tiger;
else
a = new Cow;
end
if condition2 then
f = new Meat;
else
f = new Grass;
end
a->eat(f);
The last statement of this piece of code must be checked at run-time as there
is no way to determine that combination Cow->eat(Meat) will never arise (you
would have to prove that (not (not condition1 and condition2)) always holds
which is generally undecidable at compile-time).
Eric Amiel
Author: whipp@roborough.gpsemi.com (David Whipp)
Date: Tue, 5 Apr 1994 17:05:05 GMT Raw View
In article <1994Mar30.173538.12264@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
|> In article <1994Mar30.134332.4082@fys.ruu.nl> haaf@fysau.fys.ruu.nl (Jan
|> Haveman) writes:
|> > The point I was trying to make is that you should not give the animal a
|> > public method `eat' at all. Its public methods provide services to its
|> > clients, and `eat' is not one of them. If you want to model that a cow can
|> > only eat grass you should do it in some other way.
|>
|> No matter how the name is, eat or feed, an animal cannot produce food within
|> its body. Therefore, food must be related to animal via some public feature.
|>
|> David Shang
IMHO:
The public feature should be a relationship between the potential food and the
animal. Using a relationship, one can provide a predicate such as:
CanEat(animal, food) which can be used by either the animal, the food or the
object trying to force the food down the animal's throat. Max Motovilov
made a proposal for type checking using such a predicate on comp.object at the
beginning of March.
If one can find a way of including this predicate within a function definition
within your language then it can be used automatically by the compiler to perform
type checking (eg. as a pre-condition within eiffel).
The question arises of how this could be used in a static checking environment.
After a little thought, one can see that this is not impossible. If the
predicate is based on statically known constants (including the function typeof
currently proposed for c++) then standard techniques of constant propagation can
be used. The rules for this could ?easily? be included in the language
definition, thus providing a powerful addition to languages that use static
checking. (not a proposal for the current version of the c++ standard :-)
--
David P. Whipp. <whipp@roborough.gpsemi.com>
Not speaking for: -------------------------------------------------------
G.E.C. Plessey Due to transcription and transmission errors, the views
Semiconductors expressed here may not reflect even my own opinions!
Author: shang@corp.mot.com (David L. Shang)
Date: Tue, 5 Apr 1994 22:23:59 GMT Raw View
In article <2ne65s$q6e@wsinis10.info.win.tue.nl>
hidders@wsinis10.info.win.tue.nl (Jan Hidders) writes:
> In article <1994Mar30.173538.12264@schbbs.mot.com>,
> David L. Shang <shang@corp.mot.com> wrote:
> >No matter how the name is, eat or feed, an animal cannot produce food within
> >its body. Therefore, food must be related to animal via some public feature.
>
> Well, actually a cow *can* produce food within its body :).
But a cow does not drink her own milk (for a baby cow, probably her mother's).
Just kidding.
>
> On the other hand, it might sometimes be the case that a service is not
> provided because the arguments are not of the right type. Such cases can be
> detected in advance by advanced typing systems such as presented by David
> Shang. Notice, however, that if the arguments *are* of the right type, the
> receiving object is obliged to perform the service. If the service can be
> refused for other reasons, then the method would still have to be seen
> as a request.
>
The receiving object is not obliged to perform the service even though the
arguments *are* of the right type. The cow may not be hungry. But the recieving
object should be protected from any hurt in terms of a wrong input type. There
are three ways to do that:
1. run-time type check (let the cow decide which to eat);
2. system-level type check (quite expensive way to run a farm)
3. Cluster's way (organize a farm by more accurate func. spec.)
I do not object run-time type check. It is inevitable in some cases I've
already pointed out.
The point is that run-time type check should be avoided when it is not
required.
That is why we want a typed interface. If we have function ADD with interface
(obj1, obj2), we should qualify the input by addible type, rather than a
untyped interface, leaving the resposibility to run-time type check.
For the same reason, we want an accurately typed interface to avoid some
unnecessary run-time type check. If we know that "an animal can eat all kinds
animal food" is wrong, why should we stick to the wrong input specification in
language description?
However, there is no standard to determine to which degree that a interface
should be restricted, so as to be called accurate. It all depends on the real
problem domain and the programmer's inspection. But the language should offer
options.
In case that an accurate interface is required but the language fails to
provide a way to specify such interface, system-level type check or run-time
type check is not a good idea. The inaccurate interface would cause a lots of
confusions when a user to try communicate with the interface, needless to say
the danger if the implementator happens to ignore a run-time type check.
Again, I do not oppose run-time type check, only if it is necessary.
Terefore, I do not oppose the idea to let the animal decide which to eat if you
think that this is a natual way to describe your own animal class.
Once again, the language should offer options.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 6 Apr 1994 14:21:26 GMT Raw View
In article <2nrn7b$if6@news-rocq.inria.fr> amiel@rita.inria.fr (Eric Amiel
INRIA ROCQUENCOURT) writes:
>
> When you have covariant arguments, no matter how sophisticated your type
> system/ type checker is, there are always simple cases that are beyond the
> reach of static type checking.
>
On the contrary, when you do not have covariant arguments, there are simple
cases that are beyond the reach of static type checking.
For example:
class Herbivore is Animal
function eat (food: AnimalFood)
-- no covariant argument here
begin
case (typeof(food))
PlantFood: take it
...
-- run time type check must be performed
end;
end;
As long as there is a covariant requirement in the real world and the lanugage
does not support covariant argument, there are always cases that needs run-time
type check or system level type check, which is actually not required if the
lanugage can present a proper covariant argument specification.
However, if the language supports covariant argument but doest not support it
properly, these cases cannot be eliminated.
Run-time type check is only necessary when you try to put an object from a
heterogeneous environment into a homogeneous environment. It is not necessary
for the case of covariance alone. For the example you gave as below:
> For example :
>
> Animal a;
> Food f;
>
> if condition1 then
> a = new Tiger;
> else
> a = new Cow;
> end
>
> if condition2 then
> f = new Meat;
> else
> f = new Grass;
> end
>
> a->eat(f);
>
> The last statement of this piece of code must be checked at run-time as there
> is no way to determine that combination Cow->eat(Meat) will never arise (you
> would have to prove that (not (not condition1 and condition2)) always holds
> which is generally undecidable at compile-time).
>
Even if you do not have a covariant argument specification for eat, you still
need run-time type check within the eat function body. Therefore, IT IS NOT the
covariant argument specification for eat that requires run-time type check. IT
IS the heterogeneous variable "f". You try to put f into a homogeneous
interface.
The language should be designed to eliminate the necessity of run time type
check for a homogeneous environment.
Languages that do not support covariant argument cannot eliminate the
necessity. Covariance is the requirement in the real world. Run time type check
is inevitable when such requirement comes but the language fails to meet.
Languages that do not support covariant argument properly cannot eliminate the
necessity either. Covariance is required only for homogeneous structure.
Failure to provide an accurate homogeneous interface for the homogeneous
structure will require run time type check too.
David Shang
Author: jeff@karazm.math.uh.edu (Jeff M Younker)
Date: 7 Apr 94 00:21:17 Raw View
As it stands I don't really see the need for the predicate. In Sather you
could simply define a function in the class $ANIMAL called
CanEat($FOOD):BOOL. Then every decendent *must* define a function CanEat
which will accept *any decendent* of $FOOD and return a BOOL. If the
class does not provide such a function then it *WILL NOT COMPILE*.
This is why contravariance is good. Any call that you can make on a parent
class will be valid for *all* of its decendents.
I have yet to meet a well designed program which didn't follow this
guideline.
--
jeff younker jeff@math.uh.edu
- These are my opinions. If they resemble those of the -
- University of Houston it is possible that I am very sick. -
GS/M/O d--- -p+ c++ l m*/--- s/- !g w+ t r x++
Author: jeff@karazm.math.uh.edu (Jeff M Younker)
Date: 7 Apr 94 01:05:12 Raw View
Let's face it. No matter what we do, we will not eliminate bugs by having
an absolutely air tight type system. Only the simplest ones.
There seem to be two fundamental ideas at war here. The first is the idea
that subclasses are made by restricting the restricting the actions that a
child is capable of (covariance). The second it that subclasses are made
by giving them additional abilities (contravariance).
To me the first idea doesn't make sense. If you make a subclass it should
be able to do everything that it's parents can. If you make a subclass by
restricting the parameters to its function you have defeated the purpose of
inheritance. Subclasses can no longer perform the actins that their
parents could. Could someone please enlighten me if there are advantages
to this?
--
jeff younker jeff@math.uh.edu
- These are my opinions. If they resemble those of the -
- University of Houston it is possible that I am very sick. -
GS/M/O d--- -p+ c++ l m*/--- s/- !g w+ t r x++
Author: dujardin@naima.inria.fr (Eric Dujardin)
Date: 7 Apr 1994 12:13:25 GMT Raw View
In article <JEFF.94Apr7010512@karazm.math.uh.edu>, jeff@karazm.math.uh.edu (Jeff M Younker) writes:
|>
|> To me the first idea doesn't make sense. If you make a subclass it should
|> be able to do everything that it's parents can. If you make a subclass by
|> restricting the parameters to its function you have defeated the purpose of
|> inheritance. Subclasses can no longer perform the actins that their
|> parents could. Could someone please enlighten me if there are advantages
|> to this?
|>
Subclasses can no longer perform some actions that their parent do, but
they can perform new actions : in the body of method cow::eat(grass), you
will need to invoke some methods on instances of "grass", that are not
defined on the class "food" (which method ? Well, I think that a cow's
stomach could give you all details about this...).
Eric
--
Eric Dujardin - Eric.Dujardin@inria.fr
INRIA Rocquencourt, projet RODIN "Les bons gongs font
BP 105, 78153 Le Chesnay Cedex les bonzes amis"
Tel : (33 1) 39 63 56 19 (Gotlib)
Author: whipp@roborough.gpsemi.com (David Whipp)
Date: Mon, 11 Apr 1994 14:42:39 GMT Raw View
In article <JEFF.94Apr7002117@karazm.math.uh.edu>, jeff@karazm.math.uh.edu (Jeff M Younker) writes:
|>
|> As it stands I don't really see the need for the predicate. In Sather you
|> could simply define a function in the class $ANIMAL called
|> CanEat($FOOD):BOOL. Then every decendent *must* define a function CanEat
|> which will accept *any decendent* of $FOOD and return a BOOL. If the
|> class does not provide such a function then it *WILL NOT COMPILE*.
|>
|> This is why contravariance is good. Any call that you can make on a parent
|> class will be valid for *all* of its decendents.
|>
|> I have yet to meet a well designed program which didn't follow this
|> guideline.
In my post, I suggested that the predicate described above (CanEat) should
not be part of the class $ANIMAL. You seem to agree with the concept of the
predicate, but not in where it should be.
Let me give an example of why I feel that the canEat predicate should be part of
a relationship between $FOOD and $ANIMAL, rather than part of either of the
individual classes. Imagine you (derived from $ANIMAL) are out picking wild
mushrooms ($FOOD). Do you know which fungi are edible? or do you consult a
reference book ($RELATIONSHIP) which can link the specific instance of $FOOD
(yellow bottomed, green ringed 15cm tall toadstool) withyou (male human
mathematician) and which returns FALSE.
Given that it may be desirable in some cases to use such relationships, which
are external to the classes involved, my original post just suggested that
a boolean function could still be used within a static checking environment. I
will agree that there may also be cases when the predicate will be part of
one of the classes involved, In this case, the predicate can still be part
of a static checking environment... indeed, one may be able to specialise
the concept of the type predicate into a contravariance type system.
[I've trimmed the Follow-up line to remove comp.lang.eiffel and comp.std.c++]
--
David P. Whipp. <whipp@roborough.gpsemi.com>
Not speaking for: -------------------------------------------------------
G.E.C. Plessey Due to transcription and transmission errors, the views
Semiconductors expressed here may not reflect even my own opinions!
Author: haaf@fysau.fys.ruu.nl (Jan Haveman)
Date: Tue, 29 Mar 1994 18:10:12 GMT Raw View
I haven't seen the start of this cow-animal war, but has anyone ever
considered the option that, just because a Cow is_an Animal,
you should not provide Animal with public methods, like `eat', that will
be redefined with a non-compatible argument type in Cow?
It's OK to model the real world, but your intuitive world view can be too
simple...
What you could do is call the (hidden) `eat' method through an interface
feed(f: AnyFood) return accept: bool
or let the Animal call it itself from its thread.
To stick to the real-world analogy, you can't just force an animal to eat
something by sending it the appropriate message, it should decide to do it
by itself.
Cheers, Jan.
============================================================
Jan Haveman email: haaf@fys.ruu.nl
phone: +31-30-534504
Dept. of Physics and Astronomy, Utrecht University
PO box 80.000, 3508 TA Utrecht, The Netherlands
============================================================
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 30 Mar 1994 00:20:40 GMT Raw View
In article <1994Mar29.181012.1305@fys.ruu.nl> haaf@fysau.fys.ruu.nl (Jan
Haveman) writes:
>
> To stick to the real-world analogy, you can't just force an animal to eat
> something by sending it the appropriate message, it should decide to do it
> by itself.
>
Then, why should we have function interface typed, and why should we have typed
language, if we consider that a cannon is much easier to be recognized
uneatable by an animal than something smells appetitive but actually poisonous?
David Shang
Author: berger@Informatik.TU-Muenchen.DE (Christoph Berger)
Date: 30 Mar 1994 12:18:17 GMT Raw View
In article <1994Mar29.211326.1418@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
[...]
|> > |> >
|> > |> > class BandedMatrix {
|> > |> > function mult (x: BandedMatrix): BandedMatrix;
|> > |> > }
|> > |> >
|> > |> > class TriMatrix is a BandedMatrix {
|> > |> > function mult (x: BandedMatrix): BandedMatrix;
|> > |> > }
|> > |> >
[...]
|> Covaraince is not always the accurate way. Sometimes novariance is necessary,
|> just as the example you gave.
|>
|> If the attribute type depends on the type of the enclosing object, covariance
|> is required. I refer to this structure homogeneous structure. For example, the
|> element type depends on the list type if the list is a homogeneous list which
|> contains objects in the same type.
|>
|> If the attribute type does not depend on the type of the enclosing object,
|> covariance is not required.
It seems to be not just "not required", but impossible.
|> I refer to this structure heterogeneous structure.
|> For example, the element type varies independently if the list is a
|> heterogeneous list which contains object of different types.
|>
|> In real world, both homogeneousness and heterogeneousness are required.
|> Therefore, both covaraince and novaraince are necessary. However, the real
|> world is not so simple that you can tell the property of a list from the very
|> beginning. The concept of heter/homogeneousness is relative. It depends on what
|> is the "same type". For example, a group of "bears" may be considered as a
|> homogeneous group at a level of mammal, but may be considered as a
|> heterogeneous group if we make a finer division on the concept of "bears".
|> Therefore, appointing a structure as homo or heter simply by some key words, as
|> some common approach does, is not a fine work.
|>
|> Only Cluster's classes provide a natural way to specify both the homo and the
|> heter structures. If you get my paper, refer to the section "homogeneousness
|> vs. heterogeneousness" for detail.
|>
|> Here you raise an interesting point: the return type of a matrix multiplication
|> function.
|>
|> If we assume that the matrix be a homogeneous matrix, covariance is necessary:
|>
|> class Matrix [ElementType < Number]
|> begin
|> function mult (x: thisclass): thisclass;
|> end
|>
Dut if you derive a new class from Matrix, its objects will only be able to
multiply with objects of the same class. This is not always intended, as I
showed in the example above.
This is how class Matrix may be written as a generic class in C++:
template < class T >
class Matrix {
public:
Matrix<T> mult ( const Matrix<T>& x );
}
|> But if we consider that matrixes with different clumns and lines are different
|> types, covariance is still necessary for that function, but not in terms of
|> matrix itself. The covariance is done in terms of the matrix element type:
|>
|> class Matrix
|> [ElementType < Number; Columns: cardinal; Lines: cardinal]
|> begin
|> function mult (x: Matrix[ElementType=thisclass.ElementType]):
|> Matrix[ElementType=thisclass.ElementType]
|> begin
|> if (typeof(x).Lines <> thisclass.Columns) return nil;
|> output: Matrix =
|> new Matrix
|> [ ElementType=thisclass.ElementType,
|> Columns = thisclass.Columns
|> Lines = typeof(x).Lines
|> ];
|> ...
|> return output;
|> end
|> end
|>
|> Function mult interface prohibits an input of a matrix with wrong element type.
|>
|> To explore this example further, you may find another very interesting point
|> with Cluster: dynamic typing. A new type can be created at run time. In the
|> above example, the output type is a new matrix type with columns and lines
|> calculated by the size of the input and this object.
Again, exactly this behavior can be achieved with a generic class. Rows and
columns are passed to the constructor:
template < class T >
class Matrix {
public:
Matrix(int rows, int columns);
Matrix<T> mult ( const Matrix<T>& x ) {
if rows != x.columns throw IncompatibleSize();
Matrix<T> output(columns, x.rows);
...
return output;
}
}
|> Consider another example. Suppose we have an object frame that contains a
|> number of object in a certain types, say: T1, T2, nad T3:
|>
|> class ObjectFrame [ T1, T2, T3 ]
|> begin
|> constructor (obj1:T1, obj2:T2, obj3:T3);
|> end
|>
|> You can create a new ObjectFrame class by run time value:
|>
|> var obj1: anyclass = GetAnObjectFromAnHeterDatabase;
|> obj2: anyclass = GetAnObjectFromAnHeterDatabase;
|> obj3: anyclass = GetAnObjectFromAnHeterDatabase;
|> frame: ObjectFrame =
|> new ObjectFrame
|> [ typeof(obj1), typeof(obj2), typeof(obj3) ]
|> ( obj1, obj2, obj3 );
|>
|> As far as I know, no other static language can do this way. In our experience
|> of writing object-oriented database with Cluster, this is a fundamental
|> facility. That's why people always have difficulty in using other static
|> languages to write OO databases.
I don't know the semantics of ObjectFrame, but what if all objects in the
heterogeneous database have a common superclass? Isn't it sufficient to
create an object as ObjectFrame[T](t1,t2,t3) with a statically known T?
But nevertheless, if ObjectFrame depends on the types of obj1-3, this
example is convincing.
--
| Christoph Berger berger@informatik.tu-muenchen.de |
| c.berger@ieee.org |
| Christoph_Berger@m.maus.de |
Author: haaf@fysau.fys.ruu.nl (Jan Haveman)
Date: Wed, 30 Mar 1994 13:43:32 GMT Raw View
In article <1994Mar30.002040.4063@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
> In article <1994Mar29.181012.1305@fys.ruu.nl> haaf@fysau.fys.ruu.nl (Jan
> Haveman) writes:
> >
> > To stick to the real-world analogy, you can't just force an animal to eat
> > something by sending it the appropriate message, it should decide to do it
> > by itself.
> >
>
> Then, why should we have function interface typed, and why should we have typed
> language, if we consider that a cannon is much easier to be recognized
> uneatable by an animal than something smells appetitive but actually poisonous?
The point I was trying to make is that you should not give the animal a public
method `eat' at all. Its public methods provide services to its clients, and `eat'
is not one of them. If you want to model that a cow can only eat grass you should
do it in some other way.
Author: shang@corp.mot.com (David L. Shang)
Date: Tue, 29 Mar 1994 21:13:26 GMT Raw View
In article <2n9g5j$jke@hpsystem1.informatik.tu-muenchen.de>
berger@Informatik.TU-Muenchen.DE (Christoph Berger) writes:
>
> But what is the right way to deal with covariance, given that I
> don't use Cluster? I know that the *combination* of covariance
> and precise interface definitions will do, but without this
> combination there seems to be *no* right way to deal with
> covariance.
>
I do not know whether there is other language that can deal with covariance
properly.
The other proper way I only know is the type substitution proposed by Jens
Palsberg and Michael Schwartzbach. Type-intra dependency is expressed
implicitly by penetrated substitution. By this method, AnimalFood will be
substituded with PlantFood when Animal should be substituded with
HerbivoreAnimal.
Eiffel's system level type checking, Beta's run-time type check, and the work
done by Eric Amiel (see previous post in this thread), though not perfect, are
the positive attitude to handle the covariance problem.
Novariance is kind of ostrich policy.
Contravariance and subclassing without substitubility are kind of misleading
methods.
> |> >
> |> > class BandedMatrix {
> |> > function mult (x: BandedMatrix): BandedMatrix;
> |> > }
> |> >
> |> > class TriMatrix is a BandedMatrix {
> |> > function mult (x: BandedMatrix): BandedMatrix;
> |> > }
> |> >
> |> > Sure you want to allow a tridiagonal matrix to be multiplied with a
> |> > band matrix.
> |> >
> |> This is an example of novarance, not contravariance.
>
> But nevertheless, doesn't this example reflect the problem more accurately
> than a covariant version? (Btw, covariance of return types wouldn't work
> here anyway, because TriMatrix * TriMatrix = 5-Banded-Matrix)
>
Covaraince is not always the accurate way. Sometimes novariance is necessary,
just as the example you gave.
If the attribute type depends on the type of the enclosing object, covariance
is required. I refer to this structure homogeneous structure. For example, the
element type depends on the list type if the list is a homogeneous list which
contains objects in the same type.
If the attribute type does not depend on the type of the enclosing object,
covariance is not required. I refer to this structure heterogeneous structure.
For example, the element type varies independently if the list is a
heterogeneous list which contains object of different types.
In real world, both homogeneousness and heterogeneousness are required.
Therefore, both covaraince and novaraince are necessary. However, the real
world is not so simple that you can tell the property of a list from the very
beginning. The concept of heter/homogeneousness is relative. It depends on what
is the "same type". For example, a group of "bears" may be considered as a
homogeneous group at a level of mammal, but may be considered as a
heterogeneous group if we make a finer division on the concept of "bears".
Therefore, appointing a structure as homo or heter simply by some key words, as
some common approach does, is not a fine work.
Only Cluster's classes provide a natural way to specify both the homo and the
heter structures. If you get my paper, refer to the section "homogeneousness
vs. heterogeneousness" for detail.
Here you raise an interesting point: the return type of a matrix multiplication
function.
If we assume that the matrix be a homogeneous matrix, covariance is necessary:
class Matrix [ElementType < Number]
begin
function mult (x: thisclass): thisclass;
end
But if we consider that matrixes with different clumns and lines are different
types, covariance is still necessary for that function, but not in terms of
matrix itself. The covariance is done in terms of the matrix element type:
class Matrix
[ElementType < Number; Columns: cardinal; Lines: cardinal]
begin
function mult (x: Matrix[ElementType=thisclass.ElementType]):
Matrix[ElementType=thisclass.ElementType]
begin
if (typeof(x).Lines <> thisclass.Columns) return nil;
output: Matrix =
new Matrix
[ ElementType=thisclass.ElementType,
Columns = thisclass.Columns
Lines = typeof(x).Lines
];
...
return output;
end
end
Function mult interface prohibits an input of a matrix with wrong element type.
To explore this example further, you may find another very interesting point
with Cluster: dynamic typing. A new type can be created at run time. In the
above example, the output type is a new matrix type with columns and lines
calculated by the size of the input and this object.
We can have more delicate interface for the mult method:
class Matrix
[ElementType < Number; Columns: cardinal; Lines: cardinal]
begin
function mult
(x: Matrix
[ ElementType = thisclass.ElementType;
Lines = thisclass.Columns
]
):
Matrix
[ ElementType = thisclass.ElementType;
Columns = thisclass.Columns
Lines = typeof(x).Lines
];
end
Then, the matrix size will be checked by the compiler. In practice, you may not
be willing to write such a rigorous interface, rather, write code to check the
size at run-time. Anyway, Cluster offers you options.
Consider another example. Suppose we have an object frame that contains a
number of object in a certain types, say: T1, T2, nad T3:
class ObjectFrame [ T1, T2, T3 ]
begin
constructor (obj1:T1, obj2:T2, obj3:T3);
end
You can create a new ObjectFrame class by run time value:
var obj1: anyclass = GetAnObjectFromAnHeterDatabase;
obj2: anyclass = GetAnObjectFromAnHeterDatabase;
obj3: anyclass = GetAnObjectFromAnHeterDatabase;
frame: ObjectFrame =
new ObjectFrame
[ typeof(obj1), typeof(obj2), typeof(obj3) ]
( obj1, obj2, obj3 );
As far as I know, no other static language can do this way. In our experience
of writing object-oriented database with Cluster, this is a fundamental
facility. That's why people always have difficulty in using other static
languages to write OO databases.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 30 Mar 1994 17:35:38 GMT Raw View
In article <1994Mar30.134332.4082@fys.ruu.nl> haaf@fysau.fys.ruu.nl (Jan
Haveman) writes:
> The point I was trying to make is that you should not give the animal a
> public method `eat' at all. Its public methods provide services to its
> clients, and `eat' is not one of them. If you want to model that a cow can
> only eat grass you should do it in some other way.
No matter how the name is, eat or feed, an animal cannot produce food within
its body. Therefore, food must be related to animal via some public feature.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 30 Mar 1994 18:45:06 GMT Raw View
In article <2nbqm9$lee@hpsystem1.informatik.tu-muenchen.de>
berger@Informatik.TU-Muenchen.DE (Christoph Berger) writes:
> |>
> |> If the attribute type does not depend on the type of the enclosing object,
> |> covariance is not required.
>
> It seems to be not just "not required", but impossible.
>
Agree. It's a perfect incidence: not required AND impossible
> |> If we assume that the matrix be a homogeneous matrix, covariance is
> |> necessary:
> |>
> |> class Matrix [ElementType < Number]
> |> begin
> |> function mult (x: thisclass): thisclass;
> |> end
> |>
> Dut if you derive a new class from Matrix, its objects will only be able to
> multiply with objects of the same class. This is not always intended, as I
> showed in the example above.
I said that if we assume that the matrix be a homogeneous matrix...
If you want a heterogeneous matrix, you can use Cluster as well. Or if you want
a homogeneous matrix with heterogeneous mult method, no problem, there is a way
in Cluster.
> This is how class Matrix may be written as a generic class in C++:
>
> template < class T >
> class Matrix {
> public:
> Matrix<T> mult ( const Matrix<T>& x );
> }
>
Agree. But just for this simple case, and you cannot have any polymorphism on
Matrix class.
>
> Again, exactly this behavior can be achieved with a generic class. Rows and
> columns are passed to the constructor:
>
> template < class T >
> class Matrix {
> public:
> Matrix(int rows, int columns);
> Matrix<T> mult ( const Matrix<T>& x ) {
> if rows != x.columns throw IncompatibleSize();
> Matrix<T> output(columns, x.rows);
> ...
> return output;
> }
> }
>
Try any C++ compiler, see whether you can pass it. Remember, class parameter
cannot take run time value because C++ doest not support dynamic genericity.
You should put it some way like:
template < class T, int rows, int columns >
class Matrix {
public:
template <int x_rows, int x_columns >
Matrix<T, x_rows, columns>
mult ( const Matrix<T,x_rows,x_columns>& x )
{
if rows != x_rows throw IncompatibleSize();
Matrix<T, x_rows, columns > output;
...
return output;
}
}
Try C++ compiler. I'm sure it doest not like it. Remenmber, do not try
template alone. You should also write some code to use the template. (This is a
big pain with C++ template: you do not know whether there is an error in your
template code unless have some code to use it!)
Okay, let's suppose you get some C++ compiler that can take Stroustrup's recent
proposal on member template. I doubt whether a template function can take a non
type parameter. As far as I can remember, it cannot. Besides, you have no way
to present rows, columns, x_rows, and x_columns with run-time value. (how about
the case if the output's column should be increased by one according to the
input's column?)
Anyway, you cannot write an equivalent C++ code to my second Matrix example in
Cluster.
And how about my third example? No way in C++.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 30 Mar 1994 19:59:46 GMT Raw View
In article <1994Mar30.184506.13123@schbbs.mot.com> shang@corp.mot.com (David L.
Shang) writes:
> >
> > Again, exactly this behavior can be achieved with a generic class. Rows and
> > columns are passed to the constructor:
> >
> > template < class T >
> > class Matrix {
> > public:
> > Matrix(int rows, int columns);
> > Matrix<T> mult ( const Matrix<T>& x ) {
> > if rows != x.columns throw IncompatibleSize();
> > Matrix<T> output(columns, x.rows);
> > ...
> > return output;
> > }
> > }
> >
>
> Try any C++ compiler, see whether you can pass it. Remember, class parameter
> cannot take run time value because C++ doest not support dynamic genericity.
> You should put it some way like:
>
Sorry, my above statement must be misunderstood. I rephrase it: in my second
example, I assume that Matrixes with different rows and lines are considered
different types. Therefore, the template should be written like this:
> template < class T, int rows, int columns >
> class Matrix {
> public:
> template <int x_rows, int x_columns >
> Matrix<T, x_rows, columns>
> mult ( const Matrix<T,x_rows,x_columns>& x )
> {
> if rows != x_rows throw IncompatibleSize();
> Matrix<T, x_rows, columns > output;
> ...
> return output;
> }
> }
>
> Try C++ compiler. I'm sure it doest not like it. ...
> ...
> And how about my third example? No way in C++.
>
What I am interested in using this Matrix example here is for showing the
Cluster's ability of dynamic typing, which is certainly cannot be done by C++
template.
David Shang
Author: hidders@wsinis10.info.win.tue.nl (Jan Hidders)
Date: 31 Mar 1994 11:46:36 +0200 Raw View
In article <1994Mar30.173538.12264@schbbs.mot.com>,
David L. Shang <shang@corp.mot.com> wrote:
>In article <1994Mar30.134332.4082@fys.ruu.nl> haaf@fysau.fys.ruu.nl (Jan
>Haveman) writes:
>> The point I was trying to make is that you should not give the animal a
>> public method `eat' at all. Its public methods provide services to its
>> clients, and `eat' is not one of them. If you want to model that a cow can
>> only eat grass you should do it in some other way.
>
>No matter how the name is, eat or feed, an animal cannot produce food within
>its body. Therefore, food must be related to animal via some public feature.
Well, actually a cow *can* produce food within its body :). But seriously,
the way that Jan Haveman is lookin at things is a consistent one. His main
point IMHO is that if you specify that Animal has a method 'eat(Food)' then
this is a service that *has* to be provided by every Animal. If you feel
that not every animal should provide this service for every kind of Food
(and this is very likely) then you can turn the method into a request that
the animal may decline, something like 'feed(Food)'. But the point is that
every animal would then be obliged to let itself be fed i.e. accept the
request but doesn't actually have to eat the stuff i.e. perform the service
that is requested. (Puhleeze Daisy, come and smell this lovely crisp hay.)
This seems to me a clear, simple and expressive way of modelling. Notice
that the cow may even refuse the hay for whatever reason, for example,
because is smells funny.
On the other hand, it might sometimes be the case that a service is not
provided because the arguments are not of the right type. Such cases can be
detected in advance by advanced typing systems such as presented by David
Shang. Notice, however, that if the arguments *are* of the right type, the
receiving object is obliged to perform the service. If the service can be
refused for other reasons, then the method would still have to be seen
as a request.
So, if Jan and David are disagreeing on something than it should be on
whether the gain (early detection of requests that will never be granted) is
worth the pain (typing becomes a bit more complicated).
Thank you for listening,
--
~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~`"'~
-- Jan Hidders hidders@win.tue.nl
Author: berger@Informatik.TU-Muenchen.DE (Christoph Berger)
Date: 31 Mar 1994 13:46:34 GMT Raw View
In article <1994Mar30.184506.13123@schbbs.mot.com> shang@corp.mot.com (David L.
Shang) writes:
|> > |> If we assume that the matrix be a homogeneous matrix, covariance is
|> > |> necessary:
[...]
|> > Dut if you derive a new class from Matrix, its objects will only be able to
|> > multiply with objects of the same class. This is not always intended, as I
|> > showed in the example above.
|>
|> I said that if we assume that the matrix be a homogeneous matrix...
|> If you want a heterogeneous matrix, you can use Cluster as well. Or if you want
|> a homogeneous matrix with heterogeneous mult method, no problem, there is a way
|> in Cluster.
If we assume a homogeneous matrix, your cluster class can be easily written
as a C++ class, too. No xyz-variance is needed at all (if I interpret your
definition of homogeneous right).
In article <1994Mar30.195946.14439@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
|> In article <1994Mar30.184506.13123@schbbs.mot.com> shang@corp.mot.com (David L.
|> Shang) writes:
|> > >
|> > > Again, exactly this behavior can be achieved with a generic class. Rows and
|> > > columns are passed to the constructor:
|> > >
|> > > template < class T >
|> > > class Matrix {
|> > > public:
|> > > Matrix(int rows, int columns);
|> > > Matrix<T> mult ( const Matrix<T>& x ) {
|> > > if rows != x.columns throw IncompatibleSize();
|> > > Matrix<T> output(columns, x.rows);
|> > > ...
|> > > return output;
|> > > }
|> > > }
|> > >
|> >
|> > Try any C++ compiler, see whether you can pass it. Remember, class parameter
|> > cannot take run time value because C++ doest not support dynamic genericity.
|> > You should put it some way like:
I built matrices this way. It works.
|>
|> Sorry, my above statement must be misunderstood. I rephrase it: in my second
|> example, I assume that Matrixes with different rows and lines are considered
|> different types. Therefore, the template should be written like this:
|>
|> > template < class T, int rows, int columns >
|> > class Matrix {
|> > public:
|> > template <int x_rows, int x_columns >
|> > Matrix<T, x_rows, columns>
|> > mult ( const Matrix<T,x_rows,x_columns>& x )
|> > {
|> > if rows != x_rows throw IncompatibleSize();
|> > Matrix<T, x_rows, columns > output;
|> > ...
|> > return output;
|> > }
|> > }
You are right, if I want to ape the cluster way of doing it, I have to use
non-type parameter. But what I wanted to say is that the behavior of the
cluster class is the same as the behavior of the C++ class, regardless
wether matrices of different size are considered different types. Because
wether typing or not, the decision if two matrices are size compatible
is made at runtime.
In cluster:
|> if (typeof(x).Lines <> thisclass.Columns) return nil;
In C++:
if rows != x.columns throw IncompatibleSize();
|> >
|> > Try C++ compiler. I'm sure it doest not like it. ...
No, but in this case I don't need this; It can be done as I showed above.
I really don't see any advantage of dynamic typing here.
|> > And how about my third example? No way in C++.
|> >
You're right. But without deeper knowledge about ObjectFrame, I can't say
if it isn't possible to achieve a different solution with static typing.
However, the cluster solution is really elegant.
|> What I am interested in using this Matrix example here is for showing the
|> Cluster's ability of dynamic typing, which is certainly cannot be done by C++
|> template.
My intention for giving the matrix example was to show that covariance
can be an obstacle at solving certain problems, and that novariance is as
important as covariance. Yet none of them doesn't seem to reflect the
"real world" problems in every respect.
--
| Christoph Berger berger@informatik.tu-muenchen.de |
| c.berger@ieee.org |
| Christoph_Berger@m.maus.de |
Author: Ari.Huttunen@hut.fi (Ari Juhani Huttunen)
Date: 25 Mar 94 09:06:28 GMT Raw View
In article <1994Mar24.182702.27787@schbbs.mot.com> shang@corp.mot.com (David L. Shang) writes:
! Jeff, please do not make things complicated. For people to understand the
! concept, use an example as simple as possible. We are not discussing how living
! things should be classified.
I think the point is how closely we want to model the real world in our
program. Something like "animal can eat food" is easily modelled. Something
else like "herbivores can eat plants and carnivores can eat meat" could
be modelled quite easily too. If you insist that "cows can only eat
grass and some allergic people don't eat fish", the problem is complex
and it's OK to produce a complex solution. The question is what is the
correct amount of detail you want and that depends on what you want to
do with the program.
Here's the "herbivores can eat plants and carnivores can eat meat and
omnivores can eat anything" modelled in Sather:
type $HERBIVORE is
eat($PLANT):EXCREMENT;
end;
type $CARNIVORE is
eat($MEAT):EXCREMENT;
end;
type $OMNIVORE < $HERBIVORE,$CARNIVORE is
eat($FOOD):EXCREMENT;
end;
type $FOOD is end;
type $PLANT < $FOOD is end;
type $MEAT < $FOOD is end;
Now we add a $COW that can only eat grass:
type $GRASS < $PLANT is end;
type $COW > $HERBIVORE is
eat($GRASS):EXCREMENT;
end;
Seems to work?
(BTW, if you wish to make the animals edible, you could do it the same
way Nethack code does. I.e. when the animal dies it becomes a $CORPSE
which is in the $FOOD hierarchy.)
--
--- --
-- -- Ari Huttunen ---- Never was anyone more wrong than
---- -- -- he who did nothing because he
-- ---- could only do little.
--
Author: shang@corp.mot.com (David L. Shang)
Date: Fri, 25 Mar 1994 14:53:07 GMT Raw View
In article <2mt269$c1h@deneva.sdd.trw.com> rowley@vision.trw.com (Michael T.
Rowley) writes:
> Enough talk of whether covariance is appropriate or not, let's look at
> contravariance.
>
> In article <1994Mar23.202104.14134@schbbs.mot.com>, shang@corp.mot.com (David
L. Shang) writes:
> > ... Covariance should reflect the real-world problem in
> > many case. But as to contravariance, I have not been convinced by a single
> > example yet.
>
> I'll give it a try.
>
> ...
> class HashedSet
> add(e: Hashable);
> present(e: Hashable): Boolean;
> end;
>
> class BinTreeSet
> add(e: Any);
> present(e: Any): Boolean;
> end;
>
> ...
> Perhaps this example seems contrived, since on just the basis of the
> names of the classes it doesn't seem right to say that a binary tree
> is-a hash table. But names are secondary. Is there any reason, given
> the above definitions, that a BinTreeSet _should not_ be a subtype of
> HashedSet?
Since you said that names are secondary, let's rename BinTreeSet to Set:
class Set
add(e: Any);
present(e: Any): Boolean;
end;
Does anybody accept that Set is a subtype of HashedSet?
The purpose of being a subclass is the inheritance of the properties of the
parent. If the majority cannot be inherited, what is purpose of being a
subclass?
David Shang
Author: jcm@hgc.edu (James McKim)
Date: Fri, 25 Mar 1994 17:06:58 GMT Raw View
In article <2mt269$c1h@deneva.sdd.trw.com> rowley@cs.ucla.edu writes:
>Enough talk of whether covariance is appropriate or not, let's look at
>contravariance.
>
>In article <1994Mar23.202104.14134@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
>> ... Covariance should reflect the real-world problem in
>> many case. But as to contravariance, I have not been convinced by a single
>> example yet.
>
>I'll give it a try.
>
>First, assume subtyping implies substitutability.
>Consider the following two classes:
>
>class HashedSet
> add(e: Hashable);
> present(e: Hashable): Boolean;
>end;
>
>class BinTreeSet
> add(e: Any);
> present(e: Any): Boolean;
>end;
[..Argument that BinTreeSet should inherit from HashedSet deleted..]
This is the best example I've seen, but aside from the inheritance
hierarchy problems that Roger Browne pointed out I think there
are other problems as well.
Is there any way to retrieve an element of one of these sets? Is there
a current item, an indexed item, any way of looking at any item of
the set?
I'll assume yes until you tell me no. :-)
So what is the type of such an item?
In HashedSet, we'd expect item : Hashable and in BinTreeSet we'd expect
item : ANY. Now which way should the inheritance go?
>
>Michael Rowley
>
Regards,
-- Jim
--
*------------------------------------------------------------------------------*
Jim McKim (203)-548-2458 In exactly 3.2 seconds it will be a few
Internet: jcm@hgc.edu minutes to 5:00.
Author: scottb@puente.jpl.nasa.gov (Scott Burleigh)
Date: 25 Mar 1994 17:22:15 GMT Raw View
In article <ARI.HUTTUNEN.94Mar25110629@delta.hut.fi>, Ari.Huttunen@hut.fi (Ari Juhani Huttunen) writes:
|> In article <1994Mar24.182702.27787@schbbs.mot.com> shang@corp.mot.com (David L. Shang) writes:
|>
|> ! Jeff, please do not make things complicated. For people to understand the
|> ! concept, use an example as simple as possible. We are not discussing how living
|> ! things should be classified.
|>
|> I think the point is how closely we want to model the real world in our
|> program. Something like "animal can eat food" is easily modelled. Something
|> else like "herbivores can eat plants and carnivores can eat meat" could
|> be modelled quite easily too. If you insist that "cows can only eat
|> grass and some allergic people don't eat fish", the problem is complex
|> and it's OK to produce a complex solution. The question is what is the
|> correct amount of detail you want and that depends on what you want to
|> do with the program.
Yes. You can do a lot with a type system, but type safety isn't the only way
to get a program to work.
Let's suppose that any herbivore can eat any vegetable food, that any carnivore
can eat any meat, and that all food is either vegetable or meat. (These seem
to be the unstated assumptions in the discussion I've seen on this thread so
far.) Then a simple solution in C++ is:
class FoodItem
{
// whatever
};
class VegetableItem : public FoodItem
{
// whatever
};
class MeatItem : public FoodItem
{
// whatever
};
class Animal
{
public:
virtual void eat(MeatItem&) = 0;
virtual void eat(VegetableItem&) = 0;
};
class Herbivore : public Animal
{
public:
void eat(MeatItem& x) { /* reject x */ };
void eat(VegetableItem& x) { /* accept x */ };
};
This works because the problem you're dealing with is so (artificially)
simple that you can manage the whole thing through the type system.
Reality is a little more complex: some herbivores can eat plants that are
poisonous to others. That is, to solve the real problem you have to know
more about x inside eat(); specifically, you have to know what type of
plant x is. You need to interrogate the specific type of x.
You could write different virtual eat() functions for every different type
of plant there is, and thereby still handle the whole thing through the
type system, but you'd end up with an awfully big interface. This would
be kind of compulsive behavior, I think.
Instead, you could permit the Animal to inquire as to the precise type of
VegetableItem it is being asked to eat, and respond accordingly. But if
you're going to allow yourself to make this sort of type inquiry at run
time, then you've opened the whole RTTI Pandora's Box. Horrors. In that
case, you might as well acknowledge that this particular problem is just
too complex to be managed through type safety. This enables you to do
something simpler altogether:
class Animal
{
public:
virtual void eat(FoodItem&) = 0;
};
class Herbivore : public Animal
{
public:
void eat(FoodItem& x) { /* reject or accept, depending
on the type of x */ };
};
This works fine. You get no contradictions. You sacrifice a little
theoretical elegance, but on the other hand you do actually solve the
problem.
Author: shang@corp.mot.com (David L. Shang)
Date: Fri, 25 Mar 1994 20:25:17 GMT Raw View
In article <ARI.HUTTUNEN.94Mar25110629@delta.hut.fi> Ari.Huttunen@hut.fi (Ari
Juhani Huttunen) writes:
> I think the point is how closely we want to model the real world in our
> program.
Okay, let's see how closely Sather models the real world:
>
> Here's the "herbivores can eat plants and carnivores can eat meat and
> omnivores can eat anything" modelled in Sather:
>
> type $HERBIVORE is
>
> eat($PLANT):EXCREMENT;
>
> end;
>
> type $CARNIVORE is
>
> eat($MEAT):EXCREMENT;
>
> end;
>
> type $OMNIVORE < $HERBIVORE,$CARNIVORE is
>
> eat($FOOD):EXCREMENT;
>
> end;
>
> type $FOOD is end;
>
> type $PLANT < $FOOD is end;
>
> type $MEAT < $FOOD is end;
>
> Now we add a $COW that can only eat grass:
>
> type $GRASS < $PLANT is end;
>
> type $COW > $HERBIVORE is
>
> eat($GRASS):EXCREMENT;
>
> end;
>
> Seems to work?
I am completely confused in this relation:
$COW > $HERBIVORE
and
$GRASS < $PLANT
It is kind of concept abuse, regardless of the organization of the real world.
Sather applies contravariance, and meanwhile separates the concept of subclass
and subtype. Just adds ice to snow.
Either let herbivore animal eat cannon, or let animal be subtype of the
herbivore.
Either let child die, or let child be parent's parent!
David Shang
Author: bwh@beach.cis.ufl.edu (Brian Hook)
Date: 26 Mar 1994 00:48:48 GMT Raw View
In article <2mv6k7$nu0@elroy.jpl.nasa.gov> scottb@puente.jpl.nasa.gov (Scott Burleigh) writes:
> Instead, you could permit the Animal to inquire as to the precise type of
> VegetableItem it is being asked to eat, and respond accordingly. But if
> you're going to allow yourself to make this sort of type inquiry at run
> time, then you've opened the whole RTTI Pandora's Box. Horrors. In that
> case, you might as well acknowledge that this particular problem is just
> too complex to be managed through type safety. This enables you to do
> something simpler altogether:
> This works fine. You get no contradictions. You sacrifice a little
> theoretical elegance, but on the other hand you do actually solve the
> problem.
And I think that sums up the whole RTTI argument pretty well -- it seems
that way too many put too much stock in the OO dogma of "you should never
have to inquire an object about its type", which we this example shows is
complete bullcaca.
When you sorting a bunch of apples and oranges, it just doesn't make sense
to NOT inquire about type, and the same applies here -- cows don't just
"eat food" and not care about type. They look at it and say "hey, this is
grass, thus I shall eat it" -- they don't, as some OOP purists would have
it, just eat object X and let X's intrinsic behaviour define what happens.
Brian
--
+---------------------------------------------------------------+
| Brian Hook | Specializing in real-time 3D graphics |
| Box 90315 |---------------------------------------|
| Gainesville, FL 32607 | Internet: bwh@cis.ufl.edu |
+---------------------------------------------------------------+
Author: neil@aldur.demon.co.uk (Neil Wilson)
Date: Sat, 26 Mar 1994 08:35:02 +0000 Raw View
David L. Shang (shang@corp.mot.com) wrote:
: The trick in the above example is: the covariance ball is kicked to an
: attribute (member object) from the eat input. But the problem is still there:
: the type of attribute food is not declared correctly. It should be:
: class ANIMAL
: feature
: food: like current's FOOD_TYPE;
: ...
: end -- class ANIMAL
How can a derivative know current's FOOD_TYPE if you don't tell it? At
some point in time you have to tell the computer the relationship
between ANIMALs and FOOD_TYPEs. That's what I did.
A like definition ties the interface of the feature to the 'Current'
type of food. It means exactly the same as what you have written above,
if you understand the nature of 'like' definitions. What we have here is
a simple syntax issue - the semantics seem to me to be the same.
: We are not talking about the implementation issue. We are talking about the eat
: interface. Whether eat code should be rewiritten or changed is the issue of
: implementaion.
I was talking about the interface, only the interface is affected by
'like' definitions.
--
Neil Wilson (neil@aldur.demon.co.uk) +44 532 832000 x2242
6 Spring View, Ossett, Yorkshire, WF5 0QB, UK
...Arrive without travelling, see all without looking...
Author: shang@corp.mot.com (David L. Shang)
Date: Tue, 22 Mar 1994 18:54:33 GMT Raw View
Alas, I have to give up in trying to let my pony be an
animal. Why cann't I prohibit somebody to feed my pony
with flesh just because pony is an animal and flesh is
kind of animal food? I have to say: my pony is not an
animal! I won't reuse animal definition for my pony!
What's wrong? Let's have an example:
class Animal
{ function eat (food: AnimalFood);
};
Class Animal has a method eat, which takes animal food as its input. In a
herbivore animal class, the animal food should be specialized to plant food:
class HerbivoreAnimal is an Animal
{ function eat (food:PlantFood);
};
Now we have an animal variable:
var anAnimal: Animal;
When the varaible refers to some animal, we can feed it:
anAnimal.eat(flesh);
Is it right? Sure, it is. The variable anAnimal is declared as an Animal, and
the eat interface in class Animal says that any animal food is okay for its
input.
Again, is it right? No, it is not. If the variable anAnimal happens to take a
herbivore animal, oops, it's going to choke.
Solution 1: Let Herbivore Animal Eat Flesh
Some people argue that it is not right to specialize the food type for a
herbivore animal to eat. If you do not specialize the food, there is no problem
in your program then. Languages like C++ and Modula-3 prohibit such
specialization.
Why no problem now? Because you let your herbivore animal be able to eat flesh!
Yes, there is no problem with my program. But there is a problem with my
herbivore animal. My program does not reflect the real world.
Solution 2: Let Herbivore Animal Eat Anything
Some languages even go to an opposite extreme. You cannot specialize the food
type, but you can generalize the food type. In class HerbivoreAnimal, you can
specify its food as any thing, but you cannot limit it to specific animal food.
It doesn't matter even if it is not food at all. Languages like Sather, POOL-I,
and Trellis let you do this way. This approach leads to a long debate between
covariance and contravariance.
Do you feel that contravariance is better than novariance in languages like C++
or Modula-3? I don't. By contravariance, now I can let my herbivore animal be
able to eat any thing: stone, knife, cannon, needless to say flesh.
Solution 3: Animal Cannot Eat
Some people said that you should not declare eat method in animal. It should be
declared in subclasses respectively. Then, eat is not a property of an animal,
that is, an animal cannot eat! Obviously, this is not a solution. It eliminates
many opportunities of code reuse.
Solution 4: Herbivore Animal Is Not an Animal
Then, some other people said that a herbivore animal is not an animal because
it does not eat something that some other animals eat.
What? You may not accept this argument. But wait, there is a compromise: a
herbivore animal is not a subtype of animal but they can still be a subclass of
animal. To be more specific, the approach tells you that in the case of a
covariantly typed methods, the subclass is not a subtype. Therefore, a
herbivore animal is not a subtype of animal, but it can still reuse the animal
code by being a subclass.
The F-bounded polymorphism is established on this belief. A number of languages
are designed on this thoery. ABEL and TOOPL are examples. The latest version of
Eiffel will tell you that subclasses which are subtypes by declaration but not
in terms of the interface which is supported.
Being not a subtype of animal, a herbivore animal cannot be assigned to a
animal variable. An animal cannot represents a herbivore animal. After all, a
herbivore animal is not an animal. You cannot have a polymorphic all-animal
interface that can be shared and reused by other software components which are
supposed independent to a concrete animal. This certainly affects the software
reusability.
Separating the subtype concept from subclass signaficantly complicates the
concept. When the compiler tells you that a herbivore animal is not a subtype
of an animal, you'll definitely have a hard time to figure it out why not.
This approach also complicates the language compiler. It requires system-level
type checking, which gives up on local checkability. It has been proved that to
determine whether a type is a subtype of another is undecidable in general.
It is an open question that whether this approach is able to work on the open
world assumption, the essential assumption for developing large-scale software.
Eifel's system-level type-checking works only on the closed world assumption,
an assumption usually works for small toy programs.
The Right Solution: Let the Food Type Depend on the Animal Type
A herbivore animal should eat pland food only. A herbivore animal should be an
animal and a animal variable should be able to represent a herbivore animal.
It is not the problem that we specialize the food in herbivore animal, nor the
problem that we consider a herbivore animal being an animal.
The problem is the eat method interface defined in Animal class. It is not a
precise interface.
Given a class C and a protocol (method interface) P defined in C, for each
object O that belongs to C, P should be applicable to O.
Obviously, the method eat defined in class Animal is not applicable to each
individual animal because it can accept any type of food.
The problem comes from the type intra-dependency, which has not been fully
recognized so far. Oftentimes, the type of a component or the protocol of a
method interface depends on the type of the enclosing object. I call this
phenomenon as type intra-dependency (refer to my paper: Type-Safe Redefinition
in Proc. of 3rd Intl. Conf. on Software Engineering and Knowledge Engineering
for detail).
The food class in method eat should be specified as a variable to the class of
Animal:
class Animal
{ function eat (food: thisclass'Food);
};
that is, the food for the animal to eat should be of the type edible to the
animal, instead of any food.
When subclasses are derived, the class of food is automatically redefined in
covariance with animal classes. Since the interface is accurate, local type
checkability is always ensured.
A language named Cluster can reflect this dependency very well. The language
was first introduced in SIGPLAN Notices, Jan. 1991. At the time I wrote this
paper, I did not realized the covariance problem yet. Therefore, when you read
the paper, you may not find the direct anwser.
We are looking for university reseach groups working on new OO typed languages.
If you are interested in any cooperation, contact me.
David Shang
Author: berger@Informatik.TU-Muenchen.DE (Christoph Berger)
Date: 23 Mar 1994 15:21:28 GMT Raw View
In article <1994Mar22.185433.27175@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
|>
|> Alas, I have to give up in trying to let my pony be an
|> animal. Why cann't I prohibit somebody to feed my pony
|> with flesh just because pony is an animal and flesh is
|> kind of animal food? I have to say: my pony is not an
|> animal! I won't reuse animal definition for my pony!
|>
|> What's wrong? Let's have an example:
|>
|> class Animal
|> { function eat (food: AnimalFood);
|> };
|>
|> Class Animal has a method eat, which takes animal food as its input. In a
|> herbivore animal class, the animal food should be specialized to plant food:
|>
|> class HerbivoreAnimal is an Animal
|> { function eat (food:PlantFood);
|> };
|>
|> Now we have an animal variable:
|>
|> var anAnimal: Animal;
|>
|> When the varaible refers to some animal, we can feed it:
|>
|> anAnimal.eat(flesh);
|>
|> Is it right? Sure, it is. The variable anAnimal is declared as an Animal, and
|> the eat interface in class Animal says that any animal food is okay for its
|> input.
|>
|> Again, is it right? No, it is not. If the variable anAnimal happens to take a
|> herbivore animal, oops, it's going to choke.
So may I draw the conclusion that covariance of parameter types doesn't work
as it is expected to?
You can get the same (not desired) behaviour with novariance (and a little
bit RTTI):
class HerbivoreAnimal {
function eat (food: AnimalFood) {
if food.type <> PlantFood then choke;
...
}
}
The only benefit of covariance I can see here is avoiding the extra if-then
clause.
|> Solution 1: Let Herbivore Animal Eat Flesh
[...]
|> Solution 2: Let Herbivore Animal Eat Anything
[...]
|> Solution 3: Animal Cannot Eat
[...]
|> Solution 4: Herbivore Animal Is Not an Animal
[...]
|> The Right Solution: Let the Food Type Depend on the Animal Type
But this is what you already did in the code above --- so the problem is its own
solution!? Or did I miss something?
|> A herbivore animal should eat pland food only. A herbivore animal should be an
|> animal and a animal variable should be able to represent a herbivore animal.
|>
|> It is not the problem that we specialize the food in herbivore animal, nor the
|> problem that we consider a herbivore animal being an animal.
|>
|> The problem is the eat method interface defined in Animal class. It is not a
|> precise interface.
^^^^^^^^^^^^^^^^^
I think this is the point.
|> The food class in method eat should be specified as a variable to the class of
|> Animal:
|>
|> class Animal
|> { function eat (food: thisclass'Food);
|> };
|>
|> that is, the food for the animal to eat should be of the type edible to the
|> animal, instead of any food.
|>
|> When subclasses are derived, the class of food is automatically redefined in
|> covariance with animal classes. Since the interface is accurate, local type
|> checkability is always ensured.
Refering to your subject, it seems that neither contravariance nor
covariance can reflect real-world problems accurately. Maybe your
proposed solution with precise interface definition will do it, but
still we have to wait for this feature to appear in today's popular
OO-languages (or do you think a brand-new laguage will make its way
to the top only for its notion of covariance with precise interfaces?).
--
| Christoph Berger berger@informatik.tu-muenchen.de |
| c.berger@ieee.org |
| Christoph_Berger@m.maus.de |
Author: amiel@rita.inria.fr (Eric Amiel INRIA ROCQUENCOURT)
Date: 23 Mar 1994 17:02:40 GMT Raw View
We have been working for a year on this kind of problems.
We refer to them as "exceptions to schema consistency" or
"exceptions to subtyping". The idea is that sometimes a
type B that you would like to consider as a subtype of type
A is not "exactly" a subtype, that is there are exceptions.
This is the case with HerbivoreAnimal which IS-AN animal
in most respects EXCEPT for what it eats. In this case,
strictly speaking, we are dealing with "behavioral
similarity" rather than subtyping. There are many examples
of behavioral similarity : the infamous Bird and Penguins,
subtyping between parametric collections (a list of
Employees is not strictly a subtype of a list of Persons).
Exceptions are what's real life is made of.
There exist two attitudes when dealing with behavioral
similarity. First, the permissive one (which is the case
with most systems) : trust the user. Second, the restrictive
one : forbid subtyping between similar types.
We advocate a median approach, which basically consists
in allowing subtyping between similar types, but using
dynamic type checking to ensure type safety. to this end,
our type checker inserts a "check statement" around unsafe
statements.
For example, the following statement would be found to be unsafe (as anAnimal
may be HerbivoreAnimal at run-time) :
anAnimal.eat(flesh)
and surrounded by a CHECK statement :
CHECK anAnimal MAY BE APPLIED eat(flesh)
anAnimal.eat(flesh)
ELSE
/* default error message or user-provided
exception-handling code */
END
CHECK statements warn the user of possible run-time type errors and let him
provide exception-handling code.
If anyone is interested, I can send a paper about this approach (currently
submitted to a database conference). It is part of my Phd thesis that I am
about to complete in two months (with the help of G od ! and it will take
Him).
Eric Amiel
Author: olm@daimi.aau.dk (Ole Lehrmann Madsen)
Date: 23 Mar 1994 19:06:30 GMT Raw View
Thus spake berger@Informatik.TU-Muenchen.DE (Christoph Berger):
>In article <1994Mar22.185433.27175@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
>|>
>|> Alas, I have to give up in trying to let my pony be an
>|> animal. Why cann't I prohibit somebody to feed my pony
>|> with flesh just because pony is an animal and flesh is
>|> kind of animal food? I have to say: my pony is not an
>|> animal! I won't reuse animal definition for my pony!
>|>
>|> What's wrong? Let's have an example:
>|>
>|> class Animal
>|> { function eat (food: AnimalFood);
>|> };
>|>
>|> Class Animal has a method eat, which takes animal food as its input. In a
>|> herbivore animal class, the animal food should be specialized to plant food:
>|>
>|> class HerbivoreAnimal is an Animal
>|> { function eat (food:PlantFood);
>|> };
>|>
>|> Now we have an animal variable:
>|>
>|> var anAnimal: Animal;
>|>
>|> When the varaible refers to some animal, we can feed it:
>|>
>|> anAnimal.eat(flesh);
>|>
>|> Is it right? Sure, it is. The variable anAnimal is declared as an Animal, and
>|> the eat interface in class Animal says that any animal food is okay for its
>|> input.
>|>
>|> Again, is it right? No, it is not. If the variable anAnimal happens to take a
>|> herbivore animal, oops, it's going to choke.
>So may I draw the conclusion that covariance of parameter types doesn't work
>as it is expected to?
>You can get the same (not desired) behaviour with novariance (and a little
>bit RTTI):
> class HerbivoreAnimal {
> function eat (food: AnimalFood) {
> if food.type <> PlantFood then choke;
> ...
> }
> }
>The only benefit of covariance I can see here is avoiding the extra if-then
>clause.
Being a strong supported of covariance, I liked the article by Shang.
BETA has covariance and generates run-time checks to handle the problems
with not being able to perform static type checking. This kind of
run-time checking is quite similar to the run-time checking being
performed for reverse assignment in Simula and BETA.
I.e. the extra -if-then-clause' mentioned above is inserted by the BETA
compiler, but with a warning to the programmer.
The handling of covariance in BETA is described in
O.L. Madsen, B. Magnusson, B. Moller-Pedersen:
Strong Typing of Object-oriented Languages Revisited
OOPSLA'90
If you are interested in BETA in general see the book
O.L. Madsen, B. Moller-Pedersen, K. Nygaard:
Object-Oriented Programming in the BETA Programming Language
Addison Wesley, 1993, ISBN 0-201-62430-3
If you are interested in the Mjolner BETA System software send e-mail to
info@mjolner.dk
Sincerely
Ole Lehrmann Madsen
>--
>| Christoph Berger berger@informatik.tu-muenchen.de |
>| c.berger@ieee.org |
>| Christoph_Berger@m.maus.de |
Author: jjb@watson.ibm.com (John Barton)
Date: Wed, 23 Mar 1994 19:20:13 GMT Raw View
Believe it or not, there is a C++ issue in here I think...
In article <1994Mar22.185433.27175@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
[...some pony stuff deleted...]
|> {...] Let's have an example:
|>
|> class Animal
|> { function eat (food: AnimalFood);
|> };
|>
|> Class Animal has a method eat, which takes animal food as its input. In a
|> herbivore animal class, the animal food should be specialized to plant food:
|>
|> class HerbivoreAnimal is an Animal
|> { function eat (food:PlantFood);
|> };
|>
|> Now we have an animal variable:
|>
|> var anAnimal: Animal;
|>
|> When the varaible refers to some animal, we can feed it:
|>
|> anAnimal.eat(flesh);
|>
|> Is it right? Sure, it is. The variable anAnimal is declared as an Animal, and
|> the eat interface in class Animal says that any animal food is okay for its
|> input.
|>
|> Again, is it right? No, it is not. If the variable anAnimal happens to take a
|> herbivore animal, oops, it's going to choke.
|>
|>
[...strawmen deleted...]
|>
|> The food class in method eat should be specified as a variable to the class of
|> Animal:
|>
|> class Animal
|> { function eat (food: thisclass'Food);
|> };
|>
|> that is, the food for the animal to eat should be of the type edible to the
|> animal, instead of any food.
|>
|> When subclasses are derived, the class of food is automatically redefined in
|> covariance with animal classes. Since the interface is accurate, local type
|> checkability is always ensured.
|>
How about this:
template<class Kind>
class Animal {
public:
typedef Kind::Food; // Proposed to ANSI, a forward decl. of a nested type.
void eat(Kind::Food* food);
};
class Flesh {};
class PlantFood { };
class HerbivoreAnimal :
public Animal<HerbivoreAnimal> {
public:
typedef PlantFood Food; // I am a herbivore.
};
template<class Kind>
void zookeep(Animal<Kind>& a, Kind::Food* f) {
// Here is a client that knows nothing about Herbivores
a.eat(f); // no choke, always the right type of Food.
}
int main() {
HerbivoreAnimal pony;
PlantFood* hay = new PlantFood;
zookeep(pony, hay); // ok
// zookeep(pony, new Flesh()); // compile error.
return 0;
}
--
John.
John J. Barton jjb@watson.ibm.com (914)784-6645
H1-C13 IBM Watson Research Center P.O. Box 704 Hawthorne NY 10598
Author: shang@corp.mot.com (David L. Shang)
Date: Wed, 23 Mar 1994 20:21:04 GMT Raw View
In article <2mpmpo$o96@hpsystem1.informatik.tu-muenchen.de>
berger@Informatik.TU-Muenchen.DE (Christoph Berger) writes:
> So may I draw the conclusion that covariance of parameter types doesn't work
> as it is expected to?
> You can get the same (not desired) behaviour with novariance (and a little
> bit RTTI):
>
> class HerbivoreAnimal {
> function eat (food: AnimalFood) {
> if food.type <> PlantFood then choke;
> ...
> }
> }
>
> The only benefit of covariance I can see here is avoiding the extra if-then
> clause.
>
First, the matter is not that whether RTTI should be applied, or whether the
extra if-then clause can be omitted. The matter is that whether your class
specification reflect the real problem domain. Novariance is definitely not a
correct specification when covariance is required.
Second, avoiding the extra if-then clause is not the benefit of covariance
itself. On the contrary, improper way to deal with covariance needs a great
effort to have global type check in compiler or run-time type check. The
benefit can only get from a proper way to deal with covariance.
> But this is what you already did in the code above --- so the problem
> is its own solution!? Or did I miss something?
>
I'm sorry, I cannot follow what you stated here. Can you make it clear? Either
you or I missed something.
>
> Refering to your subject, it seems that neither contravariance nor
> covariance can reflect real-world problems accurately.
This is not my subject. Covariance should reflect the real-world problem in
many case. But as to contravariance, I have not been convinced by a single
example yet.
> Maybe your
> proposed solution with precise interface definition will do it, but
> still we have to wait for this feature to appear in today's popular
> OO-languages (or do you think a brand-new laguage will make its way
> to the top only for its notion of covariance with precise interfaces?).
>
> --
> | Christoph Berger berger@informatik.tu-muenchen.de |
> | c.berger@ieee.org |
> | Christoph_Berger@m.maus.de |
The language is there: Cluster. And most typed OO-languages can apply this
techique. For example, Eiffel, Sather, Object Pascal, Modula-3, not C++, where
too many patches and kludges. But a C++ alike language
( (C++) - C_kludges ) ++
is possible.
F-bounded polymorphism has created many new languages, contravariance also
created many new languages. Why the notion of safe covariance with precise
interfaces cannot create a new language? Furthermore, the solution offerred in
Cluster offers a more powerful thing: dynamic typing.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Thu, 24 Mar 1994 16:37:40 GMT Raw View
In article <2mpsng$o2d@news-rocq.inria.fr> amiel@rita.inria.fr (Eric Amiel
INRIA ROCQUENCOURT) writes:
>
>
> We have been working for a year on this kind of problems.
>
> We refer to them as "exceptions to schema consistency" or
> "exceptions to subtyping". The idea is that sometimes a
> type B that you would like to consider as a subtype of type
> A is not "exactly" a subtype, that is there are exceptions.
> This is the case with HerbivoreAnimal which IS-AN animal
> in most respects EXCEPT for what it eats. In this case,
> strictly speaking, we are dealing with "behavioral
> similarity" rather than subtyping. There are many examples
> of behavioral similarity : the infamous Bird and Penguins,
> subtyping between parametric collections (a list of
> Employees is not strictly a subtype of a list of Persons).
> Exceptions are what's real life is made of.
>
I agree that there are exceptions to be a subtype. But covariant specification
is not an exception. They are type-safe and the compiler can check it. This is
what the language Cluster has done.
>
> There exist two attitudes when dealing with behavioral
> similarity. First, the permissive one (which is the case
> with most systems) : trust the user. Second, the restrictive
> one : forbid subtyping between similar types.
> We advocate a median approach, which basically consists
> in allowing subtyping between similar types, but using
> dynamic type checking to ensure type safety. to this end,
> our type checker inserts a "check statement" around unsafe
> statements.
>
In Cluster, dynamic type checking is not neccessary for covariant
specification. It is necessary only for heterogeneous structure when the exact
type is required.
class Animal [FoodType < AnimalFood]
begin
procedure eat (food: FoodType);
end;
class HerbivoreAnimal is an Animal [FoodType < PlantFood];
If we have a procedure:
procedure feed (animal:Animal; food:typeof(animal).FoodType)
begin
animal.eat(food); -- this is type safe!
end;
Note that Animal is not a traditional generic class in Cluster, it is a real
class. You can use it without presenting the actural class parameters. Cluster
supports dynamic genericity.
Now consider an animal list:
class AnimalList [AnimalType < Animal]
begin
procedure feed (food: AnimalType.FoodType)
begin
for each animal in list
animal.eat(food);
-- this is type safe too!
end;
end;
and HerbivoreAnimalList can safely a subtype of AnimalList.
Consider a procedure further:
procedure feedGroup
( animals:AnimalList;
food:typeof(animals).AnimalType.FoodType
)
begin
animals.feed (food); -- again, this is type safe!
end;
Run-time type check is necessary only when we have to deal with heterogeneous
structure. For example:
var animal: Animal = GetAnAnimalFromAHeterogeneousDataBase;
food: AnimalFood = GetFoodFromUserInput;
when (typeof(animal).FoodType = typeof(food))
-- run time type check here
animal.eat(food);
Run-time type check is inevitable when an object should be transferred from
heterogeneous structure to a homogeneous structure. This is not the problem of
covariance. Without covariance, run time type check is still required for such
transfer. I have an example in C++:
class Node
{ Node * next;
Node * prev;
void Append( Node* new_node)
{ Node * temp = next;
next = new_node;
new_node->prev = this;
...
}
};
class TreeNode: Node
{ TreeNode * parent;
TreeNode * children;
void Append( Node* new_node)
/* no covariance here */
{ if (childre) children->Node::Append(new_node);
else ...
if (typeof(*new_node)=thisclass)
/* run-time type check must be presented here
otherwise, the program is not safe.
unfortunately, C++ cannot do that!
*/
{
new_node->parent = this;
...
}
}
};
The problem here is that Node is designed as a homogeneous structure, that is,
the type of next and prev dependeping on the type of Node. But the interface of
Append indicates wrongly that Node is a heterogeneous structure. Therefore,
problems happens when an object is transferred from this heterogeneous
interface into the homogeneous structure of Node, and run-time type check is
required.
I'd like to talk about homogeneousness or heterogeneousness in a separate
subject. They are relative concepts. Cluster has a flexible way to express
these concepts.
The key point here is: whether a type system needs run-time type check for
homogeneous structure. Without a precise interface, run-time type check is
still INEVITABLE even for homogeneous structure with or without covariance.
Recall the example above.
Eiffel system-checking is one method. This is a similar technique applied by
other languages based on F-bounded polymorphism. As I said, this method
requires a greate effort for the compiler to determine whether a subclass is a
subtype. As you said, this a restrictive method, because once an object is
determined not of a subtype, a type error is issued. In this case, a
HerbivoreAnimal cannot be an Animal.
Cluster compiler does not need to know the global type information. Local type
checkability is always ensured. For homogeneous structure, no run-time type
check is necessary. Besides, Cluster applies permissive attitude as you
defined: a HerbivoreAnimal is an Animal even though there is covariation.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Thu, 24 Mar 1994 17:36:05 GMT Raw View
In article <Cn4tpp.oJ2@hawnews.watson.ibm.com> jjb@watson.ibm.com (John Barton)
writes:
> Believe it or not, there is a C++ issue in here I think...
>
I don't think so. If you proposed some extension to C++ ... well, let's see:
> How about this:
>
> template<class Kind>
> class Animal {
> public:
> typedef Kind::Food; // Proposed to ANSI, a forward decl. of a nested type.
> void eat(Kind::Food* food);
> };
>
> class Flesh {};
> class PlantFood { };
>
> class HerbivoreAnimal :
> public Animal<HerbivoreAnimal> {
> public:
> typedef PlantFood Food; // I am a herbivore.
> };
>
> template<class Kind>
> void zookeep(Animal<Kind>& a, Kind::Food* f) {
> // Here is a client that knows nothing about Herbivores
> a.eat(f); // no choke, always the right type of Food.
> }
>
> int main() {
> HerbivoreAnimal pony;
> PlantFood* hay = new PlantFood;
> zookeep(pony, hay); // ok
> // zookeep(pony, new Flesh()); // compile error.
> return 0;
> }
>
C++ has played too many symbolic games, which makes programs very hard to read.
The above example introduces more symbolic games. It takes me quite a time to
figure out the meaning.
Basically, what you suggested is still a syntatic expansion feature. Let me
simplify the example:
template<class Food> class Animal
{
public:
void eat(Food* food);
};
class HerbivoreAnimal : public Animal<PlantFood> {}
template<class Food>
void zookeep(Animal<Food>& a, Food* f)
{
// Here is a client that knows nothing about Herbivores
a.eat(f); // no choke, always the right type of Food.
}
main()
{
HerbivoreAnimal pony;
PlantFood* hay = new PlantFood;
zookeep(pony, hay); // ok
// zookeep(pony, new Flesh()); // compile error.
}
Extension to C++ is not required for this example. But does it solve any
covariance problem? No.
First, parameterized class is not a mechanism delicate enough: it deepens the
notion of type intra-dependency only by one step. It cannot describe deeper
dependency expressions like ListType' MemberType's FoodType or CommunityType'
FamilyType's MemberType's FoodType. C++ template has an additional weekpoint.
It is not constrained. The pseudo-covariance can be achived by only one step of
template instentiation. Gradual covariance is impposible. Can your extention,
forward decalration of nested type, deal with the two things: 1. describe a
deep type intra-dependency (show me an example AnimalGroup class with a method
EatInGroup); and 2. Gradual covariance.
Second, the traditional concept of a parameterized class is not sufficient to
support convariant specification. A parameterized class is not a real class. It
cannot be used unless it is instantiated statically by presenting actual class
parameters. Since covariant specification deals with a problem of the
polymorphism in dynamic binding, the static feature of a parameterized class is
not adequate to support the polymorphism. Consider a heterogeneous animal
group:
class AnimalGroup
{ Animal * members; // this is illeagal
void accept (Animal * candidate); // this is illeagal
};
Since Animal is not a real class, we cannot use it to construct a heterogeneous
animal group that abstracts a set of polymorphic operations on its individuals
of various types as we usually did by non-generic class. This certainly reduces
the chance of code reuse in many heterogeneous cases.
Cluster parameterized class is new. It is a real class since Cluster supports
dynamic genericity. Run-time parametric polymorphism based on covariance then
is possible (It also results in a power of dynamic typing). Cluster
parameterized class also implies a metaclass, which defines a structure of a
number of classes. Therefore, deep type intra-dependency can be expressed in
terms of the type structure, for example: AnimalGroup.MemberType.FoodType.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Thu, 24 Mar 1994 17:45:37 GMT Raw View
Please note that there is an error of newsgroup description in my original post
of this thread. If you follow this description, your post will not appear in
comp.lang.eiffel, comp.lang.sather, and comp.lang.c++.
But I suugest not to distribute this thread to comp.lang.c++. Message flood
there may inundate it.
I am soory for that error.
David Shang
Author: shang@corp.mot.com (David L. Shang)
Date: Thu, 24 Mar 1994 18:27:02 GMT Raw View
In article <JEFF.94Mar23181639@karazm.math.uh.edu> jeff@karazm.math.uh.edu
(Jeff M Younker) writes:
>
>
> This discussion seems to be ignoring how things are actually done in
> Sather. We have paramaterized classes. We also have a difference between
> inheritence and implementation.
>
> I think this code should do everything that people are arguing about. It
> should also be legal in Sather.
>
[long examples deleted]
> Does this example do everything people need it to?
>
Jeff, please do not make things complicated. For people to understand the
concept, use an example as simple as possible. We are not discussing how living
things should be classified.
Sather's parameterized class is not a solution. Refer to my previous post.
Having difference between inheritence and implementation, i.e. having
subclassing without substitubility is not a good solution. It tells you that a
herbivore animal is not a animal.
David Shang
Author: rowley@vision.trw.com (Michael T. Rowley)
Date: 24 Mar 1994 21:54:17 GMT Raw View
Enough talk of whether covariance is appropriate or not, let's look at
contravariance.
In article <1994Mar23.202104.14134@schbbs.mot.com>, shang@corp.mot.com (David L. Shang) writes:
> ... Covariance should reflect the real-world problem in
> many case. But as to contravariance, I have not been convinced by a single
> example yet.
I'll give it a try.
First, assume subtyping implies substitutability.
Consider the following two classes:
class HashedSet
add(e: Hashable);
present(e: Hashable): Boolean;
end;
class BinTreeSet
add(e: Any);
present(e: Any): Boolean;
end;
Where Any is, as in Eiffel, an ancestor of all other classes.
Question: can one of these types be considered a subtype of the other?
Based on the substitutability of subtypes, it would appear that for
the simple definitions above, BinTreeSet is a subtype of HashSet.
If a program segment uses a variable that is of type HashSet, then the
code should continue work even if the instance refered to by the
variable is actually a BinTreeSet. The compiler will have mandated
that the parameters passed to the methods of this object are
Hashable, since the variable may sometimes refer to a HashedSet. But
that restriction won't prevent the code from working for BinTreeSet
instances, since they don't care about the types of the parameters.
This is contravariance. The parameters of the redefined routines in
the subtype are ancestors of the original parameter types. In this
case, the subtype BinTreeSet weakens the preconditions for its
routines by not requiring that the parameters be able to provide a
hash value for themselves.
If a language used covariance, then HashedSet would be a valid subtype
of BinTreeSet. A code segment that used a variable of type BinTreeSet
but passed it a non-hashable parameter would crash as soon as an
instance of HashedSet was assigned to that variable. Link-time type
checking could detect this possibility, if it existed, but it would
not make it possible to make BinTreeSet a subtype of HashedSet.
Perhaps this example seems contrived, since on just the basis of the
names of the classes it doesn't seem right to say that a binary tree
is-a hash table. But names are secondary. Is there any reason, given
the above definitions, that a BinTreeSet _should not_ be a subtype of
HashedSet?
Michael Rowley