[comp.object] CHALLENGE: typing and reusability

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