hoelzle@neon.Stanford.EDU (Urs Hoelzle) (03/23/91)
jls@rutabaga.Rational.COM (Jim Showalter) writes: >>hoelzle@cs.stanford.edu (Urs Hoelzle) writes: >>[A good language allows you to avoid redundancy.] >Agreed. >>Current type systems are too restricted to allow this, and so you end >>up duplicating functionality in many cases. >Disagreed. In Ada, for example, you can dynamically instantiate a >sort generic with the type and comparison desired, giving you a type >model that supports what you want to be able to do. >>Thus, if you design your system correctly, ideal reusability can be >>achieved *today* in an untyped language but not in any typed language I >>know of. >Ada. I'm glad you like Ada :-) But you didn't read a crucial part of my posting: > know of. It can be achieved partially in languages with genericity. > But only partially: as soon as you also use inheritance, you get lots ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > of problems with static type systems. Note that I'm not saying Ada can't do this because it has no inheritance; I'm saying it couldn't do it even if it had inheritance. But you aren't convinced and I've heard this argument often enough, so let me give an example. Please show me how you would use the many features of Ada to write this in Ada (or any other statically-typed language): ====================== example ============================== Assume the following types: type List = generic list; understands (among others) 'concatenate': returns a new [generic] list which is the concatenation of the two argument lists" 'do': takes a procedure with one argument and applies it to all elements of the list (i.e. a generic iterator) type FooType = "object that understands 'foo'" type A = some random application type, understands (among others) 'foo' and 'display' type B = some other application type (completely unrelated to A), also understands (among others) 'foo' and 'display' Also assume that there exists a procedure someProc(l:List of FooType) which iterates through the list and may send 'foo' to some of its elements. VAR listA: "list of objects of type A" VAR listB: "list of objects of type B" VAR listC: "see below" Now, the program you're writing has to do: listA := ... // some computation to fill the list listB := ... // some computation to fill the list listC := listA.concatenate(listB); // make a new list containing As and Bs someProc(listC); // do the "foo thing" to the list listC.do(display); // display all elements of the list ====================== end of example ========================= ** So, would everybody who does *NOT* believe that dynamically-typed ** ** languages can achieve greater reusability than today's statically-typed ** ** languages PLEASE post a solution to this (written in his/her favorite ** ** programming language)??? ** To avoid misunderstandings, please note that the above example *is* type-safe in an abstract sense: no 'message not understood' error can occur because all elements of listC understand both 'foo' (which may be sent by someProc) and 'display'. However, I claim that this piece of code won't type-check in Ada, C++, Eiffel, ... -Urs -- ------------------------------------------------------------------------------ Urs Hoelzle hoelzle@cs.stanford.EDU Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305
cs450a03@uc780.umd.edu (03/26/91)
David Cok writes: >>>>>> Compiling takes too long. I want runtime syntax checking! <<<<< I know I'm going to hate myself for this in the morning, but ... Check out J, it's got that. CAUTION: the current implementation of J uses an incredibly self-referential implementation, and ranges from 1-6 orders of magnitude slower than it should (and I'm being optimistic about that 1). Also, the language definition is not very stable, and commands get moved around (renamed), and the parser changes, and so on between versions. Further, the on-line docs ought to make any algol docs look verbose and repetitive. That said, you could get a copy from watserv1.waterloo.edu (129.97.129.140) in languages/apl/j/ (subdirectory by architecture). And, I hasten to add, J NEEDS runtime syntax checking. Example: x =: 1 1 + x 2 x =: + 1 + x syntax error (Yes, folks, you can cream yourself in J in ways that nobody ever thought of... On the other hand, the underlying design is more than a little elegant. I've just got to try making a toy compiler for the language...) Raul Rockwell
sommar@enea.se (Erland Sommarskog) (03/26/91)
(For comp.lang.eiffel readers: there's been a long discussion going on in comp.lang.misc on dynamic vs. static typing.) As the first propoent of dynamic typing, Urs Hoelzle (hoelzle@neon.Stanford.EDU) gives us a concrete example where langauges with static typing is said to bite the dust. I have deferred the quote of his example to end of this article for new readers in comp.lang.eiffel. >** So, would everybody who does *NOT* believe that dynamically-typed ** >** languages can achieve greater reusability than today's statically-typed ** >** languages PLEASE post a solution to this (written in his/her favorite ** >** programming language)??? ** >... >I claim that this piece of code won't type-check in Ada, C++, Eiffel, ... Ada is out. But in Eiffel this is a piece of cake, however with one non-ignorable objection: we must change the inheritance structure. If we introduce a new class SUPER, preferably deferred (which means that no objects of this class be created), and make A and B heirs of SUPER, we can then define a list class which looks something like: class FOO_DISPLAY_LIST(T -> SUPER) -- () is easier to read than [] on my screen. export repeat LINKED_LIST, do_foo, display inherit LINKED_LIST -- or whichever list that's appropriate feature -- define iterators that call foo and display end We can now declare: ListA : FOO_DISPLAY_LIST(A); ListB : FOO_DISPLAY_LIST(B); ListC : FOO_DISPLAY_LIST(SUPER); Of course having to modify the existing classes with a common ancestor, may be out of the question, and the example says that A and B are unrelated. On the other hand, if they are going to be in the same list, they are no longer unrelated, are they? And remember that it does not suffice that they both understand Foo and Display, they have to understand them in the same way, i.e. they must have the same parameter profile in both classes. With adding the example code to our system, we do add a dependency between A and B, and in a dynamically typed language if we modify A or B we must run a test suite which covers all a referenses to A and B to enure we didn't break anything. Not only the test suite is labourous task to develop - albeit very useful in any environment - we still don't *know* that type or parameter-profile error will not occur, we can only assume. In a statically typed language, the compiler tells ensures us that these errors won't happen. Of course several other errors are still possible, but we are at least one worry shorter. Now, if modifying A and B is undesireable *and* this is a common scene, one could of course envision changes to Eiffel to alleviate the situation. One idea would be to allow a class to declare explicit heirs, which means that we could SUPER, but still leave A and B untouched. To help up our sleep at night, this sort of declaration would only be allowed in a deferred class. But to be frank, I don't find the reason for such a change compelling enough. I'm by no means an Eiffel expert, I would appreciate other Eiffel people's input on this. If you know the example, you can press "n" here. >====================== example ============================== >Assume the following types: > >type List = generic list; understands (among others) > 'concatenate': returns a new [generic] list which is the > concatenation of the two argument lists" > 'do': takes a procedure with one argument and applies it > to all elements of the list (i.e. a generic iterator) > >type FooType = "object that understands 'foo'" > >type A = some random application type, understands (among others) > 'foo' and 'display' > >type B = some other application type (completely unrelated to A), also > understands (among others) 'foo' and 'display' > >Also assume that there exists a procedure someProc(l:List of FooType) >which iterates through the list and may send 'foo' to some of its >elements. > >VAR listA: "list of objects of type A" >VAR listB: "list of objects of type B" >VAR listC: "see below" > >Now, the program you're writing has to do: > >listA := ... // some computation to fill the list >listB := ... // some computation to fill the list > >listC := listA.concatenate(listB); // make a new list containing As and Bs > >someProc(listC); // do the "foo thing" to the list > >listC.do(display); // display all elements of the list > >====================== end of example ========================= -- Erland Sommarskog - ENEA Data, Stockholm - sommar@enea.se
cok@islsun.Kodak.COM (David Cok) (03/26/91)
In article <1991Mar22.210725.29448@neon.Stanford.EDU> hoelzle@neon.Stanford.EDU (Urs Hoelzle) writes: [ discussion of static and dynamic typing ] >====================== example ============================== >Assume the following types: > >type List = generic list; understands (among others) > 'concatenate': returns a new [generic] list which is the > concatenation of the two argument lists" > 'do': takes a procedure with one argument and applies it > to all elements of the list (i.e. a generic iterator) > >type FooType = "object that understands 'foo'" > >type A = some random application type, understands (among others) > 'foo' and 'display' > >type B = some other application type (completely unrelated to A), also > understands (among others) 'foo' and 'display' > >Also assume that there exists a procedure someProc(l:List of FooType) >which iterates through the list and may send 'foo' to some of its >elements. > >VAR listA: "list of objects of type A" >VAR listB: "list of objects of type B" >VAR listC: "see below" > >Now, the program you're writing has to do: > >listA := ... // some computation to fill the list >listB := ... // some computation to fill the list > >listC := listA.concatenate(listB); // make a new list containing As and Bs > >someProc(listC); // do the "foo thing" to the list > >listC.do(display); // display all elements of the list > >====================== end of example ========================= > >** So, would everybody who does *NOT* believe that dynamically-typed ** >** languages can achieve greater reusability than today's statically-typed ** >** languages PLEASE post a solution to this (written in his/her favorite ** >** programming language)??? ** > >To avoid misunderstandings, please note that the above example *is* >type-safe in an abstract sense: no 'message not understood' error can >occur because all elements of listC understand both 'foo' (which may >be sent by someProc) and 'display'. However, I claim that this piece >of code won't type-check in Ada, C++, Eiffel, ... > > >-Urs > > >-- >------------------------------------------------------------------------------ >Urs Hoelzle hoelzle@cs.stanford.EDU >Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305 I'm not claiming to dispute that dynamically-typed languages can achieve greater resuability (nor that using gotos can lead to more compact code !). However, I do prefer static typing where it works and am interested in discovering how to make it work better. In any case Hoelzle's programming challenge could not be resisted. So given that one wanted to (had to?) use C++, here is a (partial) solution which compiles and runs on my Sun C++ 2.0 (on a Sparc). The classes involved have enough detail to make them run, and little more. At the end there are some comments about what I could and could not make work (in C++) and what that says about static typing. Better C++ programmers may have improvements. The example below seems also to be a nice natural use of multiple inheritance. // Just to keep it interesting we'll assume that classes A and B are // derived from some other, unrelated base classes. #include <stream.h> class BaseA { private: int basea; public: BaseA(int a): basea(a) {} virtual void display() { cout << ">>> BaseA "<<basea<<"\n"; } }; class BaseB { private: int baseb; public: BaseB(int a): baseb(a) {} virtual void display() { cout <<">>> BaseB "<<baseb<<"\n";} }; // We want a list of A's and B's. The defining characteristic of what // is on the list is that methods display and foo apply to it. So define // a class with those properties. There are also lists of things which just // have foo applied to them. So class Fooable { public: virtual void foo() = 0; }; class FooDisplayable: public Fooable { public: virtual void display() = 0; virtual void foo() = 0; // Sun C++ 2.0 requires this redeclaration }; // This typedef defines FooDisplayable_mfcn_ptr as a ptr to a member fcn of // FooDisplayable which takes no arguments and returns void. typedef void (FooDisplayable::*FooDisplayable_mfcn_ptr)(); typedef void (Fooable::*Fooable_mfcn_ptr)(); // Now for the lists. This would be a template, except that my compiler // does not yet support templates. So I'll make it template-like. Also, // the list element class ought to be nested. // We have lists of Fooable things and FooDisplayable things #define ELEMENT_TYPE Fooable #define List(E_TYPE) List_of_Fooable #define Listel(E_TYPE) Listel_of_Fooable #define ListIter(E_TYPE) ListIter_of_Fooable // If ## worked in the preprocessor, I'd do the above as // #define List(E_TYPE) List_of ## E_TYPE // but since that is not implemented, we'll have to do it by hand class Listel(ELEMENT_TYPE) { friend class List(ELEMENT_TYPE); friend class ListIter(ELEMENT_TYPE); friend List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) , List(ELEMENT_TYPE) ) ; private: Listel(ELEMENT_TYPE) * next; ELEMENT_TYPE * valuep; Listel(ELEMENT_TYPE)(Listel(ELEMENT_TYPE)* ptr, ELEMENT_TYPE* v): next(ptr), valuep(v) {} }; class List(ELEMENT_TYPE) { friend class Listel(ELEMENT_TYPE); friend class ListIter(ELEMENT_TYPE); friend List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) , List(ELEMENT_TYPE) ) ; private: Listel(ELEMENT_TYPE) *ptr; public: List(ELEMENT_TYPE)(): ptr(0) {} List(ELEMENT_TYPE)(Listel(ELEMENT_TYPE)* p): ptr(p) {} virtual void push(ELEMENT_TYPE* v) { ptr = new Listel(ELEMENT_TYPE)(ptr,v); } // Could do a parallel thing for non-member functions as well, // but I'll just write it for member functions: virtual void doit(Fooable_mfcn_ptr f) { Listel(ELEMENT_TYPE)* p; p = ptr; while (p!=0) { (p->valuep->*f)(); p=p->next; } } friend void operator>> (Fooable_mfcn_ptr f,List(ELEMENT_TYPE) l) { l.doit(f); } // niftier syntax }; class ListIter(ELEMENT_TYPE) { private: Listel(ELEMENT_TYPE) * pos; public: ListIter(ELEMENT_TYPE)(List(ELEMENT_TYPE) list): pos(list.ptr) {} virtual void operator++() { if (pos != 0) pos = pos->next;} virtual ELEMENT_TYPE* value() { return pos->valuep;} virtual int atend() { return (pos==0); } }; List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) a, List(ELEMENT_TYPE) b) { // a real implementation would probably need to // make copies of a and b, but I won't bother Listel(ELEMENT_TYPE)* p; p = a.ptr; while (p->next != 0) p = p->next; p->next = b.ptr; return List(ELEMENT_TYPE)(a.ptr); } #undef ELEMENT_TYPE #undef List #undef Listel #undef ListIter // Reuse the same template for a list of FooDisplayables #define ELEMENT_TYPE FooDisplayable #define List(E_TYPE) List_of_FooDisplayable #define Listel(E_TYPE) Listel_of_FooDisplayable #define ListIter(E_TYPE) ListIter_of_FooDisplayable class Listel(ELEMENT_TYPE) { friend class List(ELEMENT_TYPE); friend class ListIter(ELEMENT_TYPE); friend List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) , List(ELEMENT_TYPE) ) ; private: Listel(ELEMENT_TYPE) * next; ELEMENT_TYPE * valuep; Listel(ELEMENT_TYPE)(Listel(ELEMENT_TYPE)* ptr, ELEMENT_TYPE* v): next(ptr), valuep(v) {} }; class List(ELEMENT_TYPE) { friend class Listel(ELEMENT_TYPE); friend class ListIter(ELEMENT_TYPE); friend List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) , List(ELEMENT_TYPE) ) ; private: Listel(ELEMENT_TYPE) *ptr; public: List(ELEMENT_TYPE)(): ptr(0) {} List(ELEMENT_TYPE)(Listel(ELEMENT_TYPE)* p): ptr(p) {} virtual void push(ELEMENT_TYPE* v) { ptr = new Listel(ELEMENT_TYPE)(ptr,v); } // Could do a parallel thing for non-member functions as well, // but I'll just write it for member functions: virtual void doit(FooDisplayable_mfcn_ptr f) { Listel(ELEMENT_TYPE)* p; p = ptr; while (p!=0) { (p->valuep->*f)(); p=p->next; } } friend void operator>> (FooDisplayable_mfcn_ptr f,List(ELEMENT_TYPE) l) { l.doit(f); } // niftier syntax }; class ListIter(ELEMENT_TYPE) { private: Listel(ELEMENT_TYPE) * pos; public: ListIter(ELEMENT_TYPE)(List(ELEMENT_TYPE) list): pos(list.ptr) {} virtual void operator++() { if (pos != 0) pos = pos->next;} virtual ELEMENT_TYPE* value() { return pos->valuep;} virtual int atend() { return (pos==0); } }; List(ELEMENT_TYPE) operator + (List(ELEMENT_TYPE) a, List(ELEMENT_TYPE) b) { // a real implementation would probably need to // make copies of a and b, but I won't bother Listel(ELEMENT_TYPE)* p; p = a.ptr; while (p->next != 0) p = p->next; p->next = b.ptr; return List(ELEMENT_TYPE)(a.ptr); } #undef ELEMENT_TYPE #undef List #undef Listel #undef ListIter // Now for classes A and B // The only change made to these classes over what they would have been // is the addition of the inheritance from FooDisplayable class A: public BaseA, public FooDisplayable { private: int inta; public: A(int a): BaseA(a+100), inta(a) {} virtual void display() {cout<<"class A "<<inta<<"\n"; BaseA::display();} virtual void foo() { cout <<"foo in class A "<<inta<<"\n"; } }; class B: public BaseB, public FooDisplayable { private: int intb; public: B(int b): BaseB(b+1000), intb(b) {} virtual void display() {cout<<"class B "<<intb<<"\n"; BaseB::display();} virtual void foo() { cout <<"foo in class B "<<intb<<"\n"; } }; // Now the program to use all this. // This routine does foo on every other element of the list void someProc(List_of_Fooable& list) { ListIter_of_Fooable iter(list); // would be ListIter<Fooable> int i = 0; Fooable_mfcn_ptr f = &Fooable::foo; Fooable* v; while (! iter.atend()) { i=1-i; if (i==0) { v=iter.value(); (v->*f)(); } iter++; } } main() { List_of_FooDisplayable listA,listB,listC; // would be List<FooDisplayable> // put together some A's int i; for (i=0; i<3; i++) listA.push(new A(i)); // put together a list of B's for (i=10; i<14; i++) listB.push(new B(i)); // concatenate the lists listC = listA + listB; // apply operations to the list // someProc(listC); // Whoops!!! see discussion below someProc(*(List_of_Fooable *)&listC); //this works but is an unsafe cast listC.doit(&FooDisplayable::display); &FooDisplayable::foo >> listC ; // alternate syntax } There are two parts to this problem. PART 1: The first problem is having classes A and B which have some common behavior and making a list of things which have that shared behavior. This was fairly easily accomplished by the multiple inheritance mechanism used above in the example: Declare a class with the requisite virtual functions, declare lists of such classes, and make sure that classes A and B are inherited from these classes (as well as whatever inheritance they already had). If a programmer did not have source code access to A and B, one could still create classes MyA (MyB) which inherited from both A (B) and FooDisplayable. The programmer would have to use MyA and MyB everywhere instead of A and B (at least where things might be put on the list) and A and B would have had to declare display and foo virtual. What was the cost of having static typing? Assuming that the List stuff was in a library, and classes A and B were needed anyway, the only cost to wanting to reuse the List code for this special list was adding the class FooDisplayable and adding the multiple inheritance into each class which needs to be in the list. What did it buy us? Knowing at compile time that nothing was inadvertently added to the list which would not know about foo or display. In this case I'll take the static typing direction. Although the solution above works fine, I myself would prefer one like the one given that did not require source code access to A and B. It is possible to do that within the context of a statically typed language like C++ since the compiler can check wherever some T* (for some class T) is being used where a FooDisplayable* is wanted whether T has methods foo and display, but I'd better continue that discussion in comp.lang.c++ . PART 2: The second problem, which I do not have a solution to, is that in the context of the program above, a List_of_FooDisplayable is not a List_of_Fooable (so the commented out call of someProc does not typecheck without casting), although semantically anything that can be done to the latter can be done to the former. The simple expedient of adding List_of_Fooable as a base class to the definition of List_of_FooDisplayable will let the program typecheck (as well as removing the possibility of a template), but it then will not work. One may not be able to answer the question of when a X* can be used for a Y* for general classes, but the question restricted to generic types might be more relevant and answerable: Given that X* is a Y* (X is derived from Y) and a template class List, when may a List<X> be used in place of a List<Y>? I'd be interested in discussion or literature references (or pointers!) on this. By the way, I used a List of Fooables in the program above rather than a list of Footypes as suggested in the problem statement. Consider this situation: Footype has member foo and other things someProc takes an argument of list of Footype and does (only) foo to it someProc is called with a List_of_Displayable As the original poster pointed out, this will not give any runtime message errors in a dynamically typed system. However, I'd maintain that it is a bad use of dynamic typing. The only thing that prevents errors is the semantic knowledge that someProc only does foo and not anything else that Footype allows. If someProc were modified later by someone who did not know that it was applied to a FooDisplayable list, errors would result. By making the list element type Fooable, one captures the type requirement that only foo is applied to elements of this list, and makes the resulting design somewhat more modular. The remaining problem is then the one of the last paragraph, namely that the type system is not sophisticated enough to know that there is an obvious implicit conversion from List<FooDisplayable> to List<Fooable>. David R. Cok Eastman Kodak Company cok@Kodak.COM >>>>> Compiling takes too long. I want runtime syntax checking! <<<<<
rick@tetrauk.UUCP (Rick Jones) (03/26/91)
As a challenge to static typing adherents, hoelzle@cs.stanford.edu (Urs Hoelzle) writes: > >Note that I'm not saying Ada can't do this because it has no >inheritance; I'm saying it couldn't do it even if it had inheritance. > >But you aren't convinced and I've heard this argument often enough, >so let me give an example. Please show me how you would use the many >features of Ada to write this in Ada (or any other statically-typed >language): This looks like a classic "fallacial proof" problem! Like all such brain teasers, the fallacy is actually in the problem, not in the solution. Let me demonstrate: >====================== example ============================== >Assume the following types: > >type List = generic list; understands (among others) > 'concatenate': returns a new [generic] list which is the > concatenation of the two argument lists" > 'do': takes a procedure with one argument and applies it > to all elements of the list (i.e. a generic iterator) > >type FooType = "object that understands 'foo'" > >type A = some random application type, understands (among others) > 'foo' and 'display' > >type B = some other application type (completely unrelated to A), also !! ^^^^^^^^^^^^^^^^^^^^^^^ !! > understands (among others) 'foo' and 'display' This proposition is actually the crux. It maintains that two completely unrelated types, which _happen_ to have two features/methods of corresponding names (and we will assume signatures :-), can be used interchangeably by client code which is only interested in those features. But there is a difference between _looking_ the same and _being_ the same. Both a letter and an invoice may support an operation "post", but it doesn't mean that you are doing the same thing to both of them. The letter you post to the mail, the invoice to the ledger. It's even more subtle if you also consider an invoice to be a sub-type of letter which can also be posted to the mail. Names in themselves are not as significant as they suggest. Eiffel has demonstrated this in its handling of multiple inheritance, where it allows features to be renamed on inheritance (the _same_ feature can have a _different_ name in the context of a sub-class; how do you handle that with dynamic typing?). Names are really a syntactic convenience. It's like the "structure equivalence" problem - are two structures that look the same in fact the same and therefore interchangeable? In this example, you can only safely interchange A and B for use of "foo" and "display" if these two features _are_ related. This is an issue of analysis and design, regardless of the language tools used. The purpose of a statically typed language is to ensure that the relationship exists and is valid. The real issue is not that the solution can't be type-checked, it's that the problem itself doesn't type-check. Please note that I'm not flaming dynamic typing as a concept, it has many points in its favour. What I would say is that any well-engineered design should in principle be statically type checkable, even if you choose to implement it in a dynamically typed language. Having said all that, I agree that current statically typed languages do have problems in this area, which results from the over-tight linkage of classes and types. I have deliberately avoided saying anything about inheritance when saying that "foo" and "display" should be related in A and B. I think that this is a yawning gap in current OO technology, where all (as far as I know) statically typed languages make class and type synonymous. You can therefore only establish the required type relationship between A and B using inheritance. The missing piece is a typing system which allows classes A and B to declare that their "foo" and "display" routines _are_ the same, not just appear to be so. These classes would therefore be subtypes of some implicit type whose only operations are "foo" and "display", even where there is no inheritance relationship. With only inheritance available, you can create the implicit type as an abstract class which supports "foo" and "display", and then make A and B inherit from it (this is where MI becomes indespensable). The generic list classes then have to be written in terms of this abstract class. This is an unwieldy way of doing something conceptually much simpler. >** So, would everybody who does *NOT* believe that dynamically-typed ** >** languages can achieve greater reusability than today's statically-typed ** ^^^^^^ >** languages PLEASE post a solution to this (written in his/her favorite ** >** programming language)??? ** I agree - today's static typing technology is not fully adequate. >To avoid misunderstandings, please note that the above example *is* >type-safe in an abstract sense: no 'message not understood' error can ^^^^^^^^^ >occur because all elements of listC understand both 'foo' (which may >be sent by someProc) and 'display'. However, I claim that this piece >of code won't type-check in Ada, C++, Eiffel, ... Not the way you presented it, as I illustrated. Is there a statically type-checked language where the type hierarchy is not tied to the class hierarchy? -- Rick Jones, Tetra Ltd. Maidenhead, Berks, UK rick@tetrauk.uucp Any fool can provide a solution - the problem is to understand the problem
guido@cwi.nl (Guido van Rossum) (03/27/91)
I tried to keep quiet in this whole (interesting!) debate, but when I saw a program of over 200 lines of C++, just to demonstrate that you can do heterogeneous lists in C++ (and the example needed multiple inheritance!), I couldn't resist showing how you do this in Python, the dynamically typed language that I have designed (posted to alt.sources recently). Python currently has *no* compile-time type checking at all, which is bothersome when you are writing large programs, but quite nice for short ones. Surprise: Python is intended for short to medium sized programs, either throw-away code or prototypes, where development speed is more important than running speed. It does have a powerful library (including a portable window system interface) but I believe its library is smaller than C's. I believe that this toy program's conciseness in Python says something about Python. Whether it's dynamic types or something else, I'm not sure. (Historic note: dynamic typing in Python was probably caused more by the desire to get completely rid of variable declarations, for briefness, than by anything else; I figured I knew how to do run-time type checking, but didn't feel like implementing compile-time polymorphism. By now, many features of Python are influenced by the presence of dynamic typing, so you couldn't take it away without severely damaging the language design.) # Python supports heterogeneous lists: class A(): def init(self, ival): self.ival = ival return self def foo(self): self.ival = self.ival * 2 def display(self): print 'This is an A, its value is', self.ival class B(): def init(self, sval): self.sval = sval return self def foo(self): self.sval = self.sval + 'x' def display(self): print 'This is a B, its value is', self.sval # Create some lists of A's and B's, and a heterogeneous list Alist = [A().init(10), A().init(20)] Blist = [B().init('help'), B().init(''), B().init('x'*20)] Clist = Alist + Blist for item in Clist: item.foo() for item in Clist: item.display() # --Guido van Rossum, CWI, Amsterdam <guido@cwi.nl> # Go ahead, flame me!
hoelzle@elaine13.Stanford.EDU (urs hoelzle) (03/27/91)
In <1991Mar26.005805.1914@kodak.kodak.com> cok@islsun.Kodak.COM (David Cok) writes: > However, I do prefer static typing where it works and am interested in ^^^^^^^^^^^^^^ > discovering how to make it work better. In any case Hoelzle's programming Sure... I guess we just disagree what "works" means :-) But now to the technical points: your code example is nice, but (as you mention yourself) it doesn't solve the problem because of a typing snag, and that was exactly my point. Furthermore, it has two other problems: 1) It assumes that you have source code access to A and B. You write that > If a programmer did not > have source code access to A and B, one could still create classes MyA (MyB) > which inherited from both A (B) and FooDisplayable. The programmer would have > to use MyA and MyB everywhere instead of A and B (at least where things might > be put on the list) and A and B would have had to declare display and foo > virtual. This doesn't work as soon as you also have Lists of A (or other classes parametrized by A) in existing modules, because List[MyA] is not a subtype of List[A]. This is a result of the contravariance rule (see e.g. "A Proposal for Making Eiffel Type-Safe" by W. Cook, ECOOP'89; "Introduction to Trellis/Owl", OOPSLA'86 [I might have gotten the years slightly wrong]). The problem isn't really with contravariance, but with the way it is applied: type-checkers don't only check the validity of your program, but also the validity of *all possible variations*: someProc(List[FooDisplayable]) is illegal because someProc *could* use List::insert to insert a Fooable instead of a FooDisplayable, which would violate the type assertion for the list. HOWEVER, the existing version of someProc *does not* use any "dangerous" List operation such as insert() and is perfectly safe. But today's type checkers are unable to recognize this because they still live in a world of batch-style compilation and are prohibited from looking at the *actual* use of objects. Therefore, they have to use worst-case assumptions about the *potential* uses and unnecessarily restrict the programmer. [For Eiffel programmers: before you agree with me, let me say that what Eiffel does is *not* what I propose here. Eiffel's "solution" to the problem is, IMHO, totally wrong. :-)] > As the original poster pointed out, this will not give any runtime message > errors in a dynamically typed system. However, I'd maintain that it is a bad > use of dynamic typing. The only thing that prevents errors is the semantic > knowledge that someProc only does foo and not anything else that Footype allows. > If someProc were modified later by someone who did not know that it was applied > to a FooDisplayable list, errors would result. Sure, type errors would result in a statically-typed language, and run-time errors in a dynamically-typed language. There's no problem with that: if your change transforms a type-correct (in my sense, not C++'s) program into an unsafe program, you get an error. 2) You need a new type of iterator for every type of message you want to send to the list. This is actually orthogonal to the issue of dynamic vs. static typing since it is about closures. In Self/Smalltalk, you define one iterator which takes a block. The block is the called by the iterator, once for every element, with the element as an argument. Thus you can do list do: [ | :elem | elem foo ] "original example" list do: [ | :elem | elem bas: 15] or (assuming that prev is a local in the caller of do:) prev: nil. list do: [ | :elem | elem baz: prev. prev: elem ] You could introduce closures to C++ et al, but it's interesting to observe that no statically-typed language has them, while many dynamically-typed languages do. You could also simulate this in C++ with function pointers, but you have to "invent" a procedure for every (trivial) block... reminds me of COBOL where you were forced to write your loop bodys as separate sections ;-) -Urs PS: > >>>>> Compiling takes too long. I want runtime syntax checking! <<<<< Hey, you can have this in Self - almost every string of characters with balanced parentheses is a legal Self expression. :-)
hoelzle@elaine2.Stanford.EDU (urs hoelzle) (03/27/91)
In <3090@enea.se> sommar@enea.se (Erland Sommarskog) writes: > As the first proponent of dynamic typing, Urs Hoelzle > (hoelzle@neon.Stanford.EDU) gives us a concrete example > where langauges with static typing are said to bite the dust. Just to set the record straight: I do not claim that dynamically-typed languages are always better for all kinds of problems, or any such thing. I posted this example because someone claimed that static typing does not affect reusability, and I just couldn't let this stand unchallenged. > class FOO_DISPLAY_LIST(T -> SUPER) > -- () is easier to read than [] on my screen. > export repeat LINKED_LIST, do_foo, display > inherit LINKED_LIST -- or whichever list that's appropriate > feature > -- define iterators that call foo and display > end > > We can now declare: > ListA : FOO_DISPLAY_LIST(A); > ListB : FOO_DISPLAY_LIST(B); > ListC : FOO_DISPLAY_LIST(SUPER); This is nice, but it only addresses the trivial part of my example. The hard part comes when you actually try to use the lists you defined. I don't have an Eiffel system to try this, but I don't think this will type-check. >listC := listA.concatenate(listB); // make a new list containing As and Bs > >someProc(listC); // do the "foo thing" to the list > >listC.do(display); // display all elements of the list (Remember that "someProc" is supposed to already exist, and its parameter is a FOO_LIST, not a FOO_DISPLAY_LIST.) For all your other comments see my other posting (response to a C++ example). -Urs -- ------------------------------------------------------------------------------ Urs Hoelzle hoelzle@cs.stanford.EDU Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305
jls@rutabaga.Rational.COM (Jim Showalter) (03/28/91)
>Sure, type errors would result in a statically-typed language, and >run-time errors in a dynamically-typed language. There's no problem >with that: Uh, yes there is. The Achilles Heel of dynamically-typed languages is run-time errors. These may be fine for WIMPS interfaces on workstations: just print out "Window Manager is confused", abort, and let the user reboot the system. ;-) This gets a little trickier when the code is running embedded in a $100 million satellite orbiting Venus. -- ***** DISCLAIMER: The opinions expressed herein are my own, except in the realm of software engineering, in which case I've borrowed them from incredibly smart people.
chang@alc.com (Sehyo Chang) (03/29/91)
In article <jls.670121104@rutabaga> jls@rutabaga.Rational.COM (Jim Showalter) writes: >>Sure, type errors would result in a statically-typed language, and >>run-time errors in a dynamically-typed language. There's no problem >>with that: > >Uh, yes there is. The Achilles Heel of dynamically-typed languages is >run-time errors. These may be fine for WIMPS interfaces on workstations: >just print out "Window Manager is confused", abort, and let the user >reboot the system. ;-) > >This gets a little trickier when the code is running embedded in a >$100 million satellite orbiting Venus. >-- >***** DISCLAIMER: The opinions expressed herein are my own, except in > the realm of software engineering, in which case I've borrowed > them from incredibly smart people. You mean I never gets program crash If I write program in C,C++,Fortran,Ada? The core dump I got yesterday when my simple 'C' program crashed is total illusion? This is most illogical, crude, stupid and absurd statement I seen for long time. Boy I sure hope I don't use any software written by your company(or relying on software just because it is written in static-type language) Given any language/environment you will have possibility of generating error which was not forceen. In case of dynamic-type languages, it could be MNU(Message Not Understood) or variants; In static-typed languages, it could be pointer zapping(which is more catastrophic) or run-away recursion(which eats all available memory, have you seen Unix when all swap-swap is gone?, it is not pretty!), or end-less loop creating infinite processes?. In fact number of possible error in static-typed language is greater than number of errors possible with dynamic-typed interface. PROOF: Since size of program is finite, number of possible mismatch E(Dynamic-typed) = finite For static-typed language number of ways programs you can write is infinite and thus generate infinite errors(since due to 'halting program 'theorem', it is not possible to predict when program will stop). Thus E(Static-typed) = infinite This will compute ratio of dynamic-typed errors to static-typed errors E(Dynamic-typed) / E(Static-Typed) = finite/infinite = R Since we all know from highschool math anything divide by inifite is zero, thus R = 0 , Q.E.D. This concludes proof that errors result from dynamic-typed language is harmless since they cany only do 'limited' amount of damage!. Life is too short, let's figure out best tool for given problem. Instead of fighting stupid religious wars. -- Sehyo Chang chang@alc.com Ascent Logic Corp (408)943-0630 180 Rose Orchard Way, Suite 200 San Jose, CA 95134
boehm@parc.xerox.com (Hans Boehm) (03/30/91)
rick@tetrauk.UUCP (Rick Jones) writes: >This proposition is actually the crux. It maintains that two completely >unrelated types, which _happen_ to have two features/methods of corresponding >names (and we will assume signatures :-), can be used interchangeably by client >code which is only interested in those features. But there is a difference >between _looking_ the same and _being_ the same. >Both a letter and an invoice may support an operation "post", but it doesn't >mean that you are doing the same thing to both of them. The letter you post to >the mail, the invoice to the ledger. It's even more subtle if you also >consider an invoice to be a sub-type of letter which can also be posted to the >mail. Names in themselves are not as significant as they suggest. Eiffel has >demonstrated this in its handling of multiple inheritance, where it allows >features to be renamed on inheritance (the _same_ feature can have a >_different_ name in the context of a sub-class; how do you handle that with >dynamic typing?). Names are really a syntactic convenience. Granted, using names in this context is far from perfect. Using names and types together is a little closer, but still not perfect. But I often don't care that the two operations are in any sense the same, only that they share certain properties. For example, I might define a routine that raises a value to an integer power by repeated squaring. It should care only that I have an associative multiplication operation available, and perhaps a multiplicative identity. I should be able to invoke this operation on the string "a" and the integer 200, with concatenation acting as multiplication, in order to get a string of 200 "a"s. (In a language that doesn't allow destructive updates of strings, this may even be the most efficient way to accomplish this task.) But this doesn't mean that concatenation and integer multiplication are in any sense "the same". It also points out (as RickJones stated) that defining "the same" using inheritance is way off. Arguing that strings and integers should inherit from a class of "monoids" seems to me to be really pushing it, and requires way too much foresight by the class hierarachy designer. (Furthermore integer could inherit in two different ways from such a class. A priori, I don't know whether plus or times is the interesting operation.) I think IBMs Scratchpad II comes closest to getting this issue right, in that (as I recall) the language allows operations in algebras to be annotated with properties such as "associative". Their existence is checked in cases like the above. There is of course no way for the compiler to associate any semantics with the word "associative"; it's treated simply as an uninterpreted property that some operations are asserted to have. Hans-J. Boehm (boehm@xerox.com) Usual disclaimers ...
jls@rutabaga.Rational.COM (Jim Showalter) (03/30/91)
>I do not >know Ada (and do not really want to), Why not? I've taken the trouble to at least gain some familiarity with Eiffel, and I've of course read Meyer's excellent book. I learned about C++. What--other than a combination of ignorance and prejudice--would cause you to dismiss out of hand a language that is really quite fine indeed? Growth stops when curiosity dies. -- ***** DISCLAIMER: The opinions expressed herein are my own, except in the realm of software engineering, in which case I've borrowed them from incredibly smart people.
jls@rutabaga.Rational.COM (Jim Showalter) (03/30/91)
>You mean I never gets program crash If I write program in C,C++,Fortran,Ada? >The core dump I got yesterday when my simple 'C' program crashed is total illusion? I never said that (particularly with respect to C, which I tend to ASSUME is going to core dump when executed). Stop putting words in my mouth. >This is most illogical, crude, stupid and absurd statement I seen for long time. I never said it. You conjured up a straw man I never said, then beat me over the head for how illogical, crude, stupid, and absurd the straw man was. If anything here is illogical, crude, stupid, and absurd, it is your debating skill. >Given any language/environment you will have possibility of generating error which >was not forceen. In case of dynamic-type languages, it could be >MNU(Message Not Understood) or variants; In static-typed languages, it could be >pointer zapping(which is more catastrophic) or run-away recursion(which eats >all available memory, have you seen Unix when all swap-swap is gone?, it is not >pretty!), or end-less loop creating infinite processes?. Spurious. In a statically-typed language, you can indeed pointer-zap, infinitely recurse, etc. You can ALSO do those same things in a dynamically typed language, and, in addition, you get--at no extra charge--at least one MORE way to screw up. I don't view this as a strong argument in favor of dynamically typed languages. >This concludes proof that errors result from dynamic-typed language is harmless since >they cany only do 'limited' amount of damage!. You mean like making a $100 million satellite spiral into the sun? -- ***** DISCLAIMER: The opinions expressed herein are my own, except in the realm of software engineering, in which case I've borrowed them from incredibly smart people.
hoelzle@neon.Stanford.EDU (Urs Hoelzle) (04/01/91)
rick@tetrauk.UUCP (Rick Jones) writes: >The real issue is not that the solution can't be type-checked, it's that the >problem itself doesn't type-check. This is not true - unless we have different understandings of what "type-check" means. For me, a program "type-checks" statically (is type-safe) iff you can prove, from the text of the program alone, that no "message not understood" errors will occur at run-time, i.e. that no interfaces are violated. As explained in an earlier posting, the example *does* type-check in this sense. >Please note that I'm not flaming dynamic typing as a concept, it has many >points in its favour. What I would say is that any well-engineered design >should in principle be statically type checkable, even if you choose to >implement it in a dynamically typed language. I agree with you 100%. My point is that today's type systems disallow many legal (type-safe) programs because they are just too restricted (the type systems, not the programs ;-). -Urs -- ------------------------------------------------------------------------------ Urs Hoelzle hoelzle@cs.stanford.EDU Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305
hoelzle@neon.Stanford.EDU (Urs Hoelzle) (04/01/91)
NOTE: the following discussion is relatively long. If you don't want to read all of it, skip to the "executive summary" at the end of the article. Several people (e.g. paj@mrcu (Paul Johnson), rick@tetrauk.UUCP (Rick Jones) and bertrand@eiffel.UUCP (Bertrand Meyer)) have argued that the types A and B in my example should be related since they have common behavior. With this change, they showed how the problem can be solved using multiple inheritance. This is indeed the case, but my case rests on the claim that this is not achievable in the Real World if we assume a high degree of code reuse [I originally posted the challenge because someone had claimed that static typing has absolutely no effect on reusability.] Here's the scenario on which my claim is based: 1) You haven't written A and B yourself but bought them from someone. That is, you cannot change A or B to inherit from a common base class. I consider this as an absolutely unavoidable problem (more justification below). There is a good objection why dynamic typing wouldn't help: |> If A and B had completely independent designers then even in Smalltalk |> it is unlikely that the two classes would have this commonality: |> different names and different arguments would probably have been |> chosen. The solution to this is to subclass A and B (with classes SubA and SubB) in order to define the necessary "interface glue". In a statically typed language, SubA and SubB would both inherit from a common superclass (FooDisplayable). So far, so good - no real disadvantages for statically-typed languages. 2) Unfortunately, the solution above only works well for leaves in the inhertiance tree. If A already had subclasses C and D, it would be very tedious to subclass all of these, too. More importantly, it only works for "first-level imported types", but not for "second-level" types such as List[A]: in typed languages, you cannot pass a List[SubA] in place of a List[A]. Thus, every piece of functionality which has second-level arguments *cannot be reused*. I contend that such cases are frequent (since "container classes" (collections) are very useful.) Bertrand Meyer argues that this problem (exemplified by someProc in my example) would not occur: |> Of course, this means that B to must be a descendant of SPECIFIC, |> and Mr. Hoelzle's point is probably that this is inappropriate since |> some_proc will only be called with arguments of type LIST [A]. However |> the problem does not arise in this way in ``pure'' object-oriented |> programming of the Simula/Eiffel (and, unless I am mistaken, Smalltalk) style. |> In this world some_proc would not take an argument l of list type, |> as in Mr. Hoelzle's statement of the problem given above, but |> would be a procedure in a list class. But this is just an artifact of my simplified example (I didn't want to include additional classes), and there are plenty of scenarios where some object takes a list of objects as one of the arguments of a function (e.g. scheduler.doSomething(list_of_processes)). Thus I still contend that the problem will show up very frequently: not every function which takes a list should be defined in a list class (actually, most of them probably shouldn't). 3) The Real Problem (C) doesn't show up until you actually try to do this in the Real World (TM). If you do indeed have a high degree of code reuse, you basically compose your new program out of existing pieces of functionality. Typically, there will be dozens if not hundreds of these components in your program, selected from a library of thousands. That is, reuse is very fine-grained. In this world, the "impedance mismatch" caused by the problems outlined in 1) and 2) becomes very annoying since the main job of a new program is to pass objects (and often collections of objects) from one subpart to another, and static typing hinders this communication because of the type system's restrictions. Furthermore, it is extremely unlikely that the designers of the thousands of predefined pieces of functionality have foreseen every possible commonality between them and factored the inheritance hierarchy accordingly. In fact, I claim that there will always be different (and equally valid) conceptual views (and thus type hierarchies) of some domains. Therefore, we must assume that situations where the types A and B are unrelated (do not share the "right" supertype) are the rule rather than the exception, even in a well-structured world. As Paul Johnson correctly observes, type hierarchies must be very fine-grained in order to achieve true reusability. Most pieces of functionality depend only on subsets of broader interfaces, and it is impossible to foresee all combinations of such subsets at design time. And even if it were possible, the subtyping problems outlined above would make it hard to take advantage of the full potential for reusability. Johnson's "sibling-supertype rule" would help here, but it still exhibits the problems with "second-level types" shown in 2). Furthermore, the introduction of numerous "impedance-matching" types could impair programming productivity ("one gets buried in paperwork"). It is here where dynamically-typed languages have a subtle but important advantage. All pieces of functionality truly (per definition) depend only on that part of their argument's interface which they actually use. Thus, programs written in such languages do not experience "impedance mismatch" and can achieve maximal reuse with minimal effort; the languages do not prevent reuse because of technicalities (i.e. restrictions in the type system). [Of course, the downside of dynamically-typed languages is that they also do not prevent abuse. But this is a philosophical issue orthogonal to this discussion, and I don't want to start another language war ;-)] Executive summary: dynamically-typed languages facilitate the communication between objects, whereas today's statically-typed languages tend to create an "impedance mismatch" which can impair communication. In a world of high reusability, creating a new program is mainly the task of coordinating the communication of existing parts. Therefore, this subtle advantage of dynamically-typed languages can make all the difference in determining the degree of reusability which can be achieved. -- ------------------------------------------------------------------------------ Urs Hoelzle hoelzle@cs.stanford.EDU Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305
chip@tct.com (Chip Salzenberg) (04/02/91)
According to chang@alc.com (Sehyo Chang):
>You mean I never gets program crash If I write program in C,C++,Fortran,Ada?
No, that's not what he means. It's not what I mean, either.
Next time, instead of flaming, try reading. It's fun and educational.
--
Chip Salzenberg at Teltronics/TCT <chip@tct.com>, <uunet!pdn!tct!chip>
"All this is conjecture of course, since I *only* post in the nude.
Nothing comes between me and my t.b. Nothing." -- Bill Coderre
chip@tct.com (Chip Salzenberg) (04/02/91)
According to hoelzle@neon.Stanford.EDU (Urs Hoelzle): >Here's the scenario on which my claim is based: > >1) You haven't written A and B yourself but bought them from someone. > That is, you cannot change A or B to inherit from a common base > class. > > The solution to this is to subclass A and B (with classes SubA and > SubB) in order to define the necessary "interface glue". There is a better solution: Create an abstract GenericFoo class, which is the element type for the FooCollection. Then define subclasses of GenericFoo called FooA, FooB, etc. Here's the key part: each FooA _refers_ to an A, each FooB _refers_ to a B, etc. With this scheme, all member functions interesting to the collection (and the collection's clients) can be implemented as virtual functions defined in GenericFoo and implemented in all subclasses of GenericFoo. This alternative solves the problem of subclassing A and B, because FooA can refer as easily to a SubA as to an A. -- Chip Salzenberg at Teltronics/TCT <chip@tct.com>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre
matt@bacchus.esa.oz (Matt Atterbury) (04/02/91)
In article <1991Mar31.215721.18687@neon.Stanford.EDU> hoelzle@neon.Stanford.EDU (Urs Hoelzle) writes:
NOTE: ...
Here's the scenario on which my claim is based:
1) You haven't written A and B yourself but bought them from someone.
That is, you cannot change A or B to inherit from a common base
class. I consider this as an absolutely unavoidable problem (more
justification below).
:
:
How many of these problems would be solved if the language allowed the
addition of 'class utilities' (as described p.82 of Booch's OODwA) to
_any_ class at _any_ time (i.e. even if you don't have the source
code). I _believe_ you would still have to add such utilities to
sub-classes by hand due to Eiffel's default non-inheritance of
`methods' ? With this facility, you could make a bought-in class (BiC)
look exactly like you want it to.
You could do this now by subclassing the BiC and adding your utilities
to this new class, however the point of a HLL is to make the
programmer's life easier - consider buying an Eiffel encapsulation of
the entire X windows library and making all/many of its classes handle
the method jump_through_the_hoop !
[ a `class utility' is a non-primitive `method' which only uses
primitive methods to do its work (i.e. doesn't play with state
directly) ]
Note that I do not use Eiffel in Real Life so do not know for sure
that one can/cannot do the above. Also, I am probably commiting heresy
by not using the `right' terminology (e.g. method) :-).
--
-------------------------------------------------------------------------------
Matt Atterbury [matt@bacchus.esa.oz] Expert Solutions Australia, Melbourne
UUCP: ...!uunet!munnari!matt@bacchus.esa.oz "klaatu barada nikto"
or: ...!uunet!murtoa!bacchus.esa.oz!matt "consider this a divorce"
ARPA: matt%bacchus.esa.oz.AU@uunet.UU.NET "life? don't talk to me about life!"
gudeman@cs.arizona.edu (David Gudeman) (04/03/91)
In article <MATT.91Apr2093735@henry.bacchus.esa.oz> Matt Atterbury writes:
]
]How many of these problems would be solved if the language allowed...
The problem with this and all the other solutions is that they involve
a non-trivial amount of extra work -- work that is only necessary to
overcome problems that are caused by static typing. Rather than try
to find ways around the problem, why not eliminate the source of the
problem?
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
hoelzle@neon.Stanford.EDU (Urs Hoelzle) (04/03/91)
chip@tct.com (Chip Salzenberg) writes: >According to hoelzle@neon.Stanford.EDU (Urs Hoelzle): >>Here's the scenario on which my claim is based: >> >>1) You haven't written A and B yourself but bought them from someone. >> That is, you cannot change A or B to inherit from a common base >> class. >> >> The solution to this is to subclass A and B (with classes SubA and >> SubB) in order to define the necessary "interface glue". >There is a better solution: Create an abstract GenericFoo class, which >is the element type for the FooCollection. Then define subclasses of >GenericFoo called FooA, FooB, etc. Here's the key part: each FooA >_refers_ to an A, each FooB _refers_ to a B, etc. >With this scheme, all member functions interesting to the collection >(and the collection's clients) can be implemented as virtual functions >defined in GenericFoo and implemented in all subclasses of GenericFoo. >This alternative solves the problem of subclassing A and B, because >FooA can refer as easily to a SubA as to an A. Introducing "wrappers" has almost the same disadvantages than the first method because it only solves an *isolated* problem and not the "impedance mismatch". A few points to consider are: - Objects aren't in just one collection or used by just one subpart of the system. Several subparts work with (partially overlapping) subinterfaces of your objects. You may need different wrappers for the same object (with different type hierarchies). - Besides being cumbersome to write (wasn't OOP supposed to reduce duplication and programming time?), wrappers create problems with object identity. - Wrappers only work for "first-level" imports, i.e. when the subsystem that you're reusing gives you As and Bs rather than collection of As or Bs. This is not a realistic assumption (or else you'd be reusing only low-level subsystems). -Urs -- ------------------------------------------------------------------------------ Urs Hoelzle hoelzle@cs.stanford.EDU Center for Integrated Systems, CIS 42, Stanford University, Stanford, CA 94305