Loose type checking in E

Tyler Close tyler@lfw.org
Sat, 24 Oct 1998 08:11:02 -0400


At 01:09 AM 10/24/98 -0700, you wrote:
>On Fri, 23 Oct 1998, Tyler Close wrote:
>> 
>> I think you're still missing the magnitude of the point. There aren't any
>> other things made inconvenient by the lack of inheritance, quite the
>> opposite actually.
>
>That's the part i'm having trouble with.  What, then, of the problem
>that inheritance was specifically created to solve?

Ya, I don't know. Maybe one of the heavyweights on this list could answer
that. They all seem to have actually been right there, with Bjarne, at the
moment of conception. If I had to guess, I would say it was a desire to
create compatible types that got out of hand.

>
>That is, what do you do when someone has handed you the ability to
>create a certain type of object with some useful behaviour, and you
>just want to extend that behaviour a little?
[...]
>Lead me, as you would lead a Java-head well-steeped in the virtues

What you are talking about here is the Decorator pattern. When you are
using the Decorator pattern, you do have to manually copy the interface of
the type you are decorating to the decorated type. In certain very specific
cases, inheritance may reduce the amount of typing that you have to do.
More often, you want finer control over what happens before and after the
decorated method is called. Sometimes you even want to completely intercept
a method call. By not using inheritance, you force yourself to think
through all of these issues. I think this is a good thing.

When what you are doing is not in fact the Decorator pattern, using
inheritance will make the interface to your new type wider than what you
need. You then need to add extra code to your sub-type to make it do
something sort of reasonable with these unrelated methods. I've seen people
spend days trying to come up with some sort of clever abstract reasoning
for doing more work than is actually necessary. In my mind, this is far
worse than having to do a little bit of mindless typing (especially when
*you* happen to be one of those gifted individuals who can type like the
wind).

>                        *     *     *
>
>I will choose a question that you have likely already answered so
[...]
>could be tedious and error-prone.  How did you do this sort of
>thing without inheritance?

Actually, my GUI library shuns the type of widgets that you are talking
about, but I think I can explain the approach for these examples anyways.

For the sake of brevity, I am not going to outline the design of my GUI
library, but try to steal away enough of the core ideas to answer your
question.

Let's start off by defining an interface.

Documented somewhere is the knowledge that all graphic-like things have the
following methods:
void mouseDown(x, y)
void mouseUp(x, y)
void fit(left, top, width, height)
boolean contains(x, y)
void paint(display)

Let's assume that someone has made a type that implements this interface by
painting a label.

Now, let's make a button:
define Button(label)
{
define raised := true

to mouseDown(x, y)
{ label mouseDown(x, y) }
to mouseUp(x, y)
{ label mouseUp(x, y) }
to fit(left, top, width, height)
{ label fit(left + 1, top - 1, width - 2, height - 2) }
to contains(x, y)
{ label contains(x, y) }
to paint(display)
{ label paint(display); # paint 3d rect }
to raise(yes)
{ raised := yes }
}

Now, let's do your PushButton:
define PushButton(button)
{
to mouseDown(x, y)
{
	if(contains(x, y))
	{ button raise(false) }
	button mouseDown(x, y)
}
to mouseUp(x, y)
{ 
	button raise(true);
	button mouseUp(x, y);
}
to fit(left, top, width, height)
{ button fit(left, top, width, height) }
to contains(x, y)
{ button contains(x, y) }
to paint(display)
{ button paint(display)}
}

Now, let's do your callback:
define PingCallback(push_button)
{
define ready := false

to mouseDown(x, y)
{ 
	ready := push_button contains(x, y); 
	push_button mouseDown(x, y) 
}
to mouseUp(x, y)
{ 
	if(ready && contains(x, y)) 
	{ 
		ready := false; 
		# do something ping 
	}
	push_button mouseUp(x, y) 
}
to fit(left, top, width, height)
{ button fit(left, top, width, height) }
to contains(x, y)
{ button contains(x, y) }
to paint(display)
{ button paint(display) }
}

Now, let's do your ToggleButton:
define ToggleButton(button, button_size, label)
{
to mouseDown(x, y)
{ 
	if(contains(x, y))
	{ button raise(false) }
	button mouseDown(x, y);
	label mouseDown(x, y);
}
to mouseUp(x, y)
{ 
	button raise(true);
	button mouseDown(x, y);
	label mouseDown(x, y);
}
to fit(left, top, width, height)
{ 
	button fit(left, top, button_size, button_size);
	label fit(left + button_size + 1, top, width - button_size - 1, height);
}
to contains(x, y)
{ button contains(x, y) || label contains(x, y) }
to paint(display)
{
	button paint(display);
	label paint(display);
}
}

The button object would have been instantiated with a stub object that does
nothing. This time, you instantiate the PingCallback with a ToggleButton
instance.

I suppose you're going to have a snit about the few methods in the above
that do nothing but forward the method call. Well, I can't do anything
about that but give you a dirty look. I strongly suspect that your
inheritance implementation (I anted up, it's now your turn) would be far
more hellish to understand. I even giggle a bit at the thought of you
trying to do the ToggleButton as a sub-class of Button (you get a funny
expression on your face when you're thinking hard).

I don't know whether any of you would have noticed or not, but in a
multithreaded environment, the above code could have serialization problems
(not deadlock though). The mouse interface is a little different in my
actual GUI library.

>
>                        *     *     *
>
>But the more serious issue is the upgrade problem: what happens
>if now i want all my buttons to draw their labels in a different
>font?  Now, i didn't foresee this improvement when i wrote Button.
>
>If i had inheritance, i would only have to add one method (say,
>'setFont') to the Button, and all my various buttons would
>immediately benefit from the new functionality.

I think you're kidding yourself. The new font will have different
proportions, meaning that you will need to resize things. Suddenly changing
the size of the font without notifying sub-classes will cause funny things
indeed.

This is a good example of the larger problem of white box reuse that
inheritance encourages.

>This becomes, of course, much worse if there is a larger tree
>of delegations 

When you first design the interface, it is a good idea to add a method like:
void localize(locale)

If you are truly paranoid that you may forget something, then you can add a
general purpose tree walking method like:
void visit(visitor)
that uses the Visitor design pattern.

>                        *     *     *
>
>> It is informative to note that only two of the Design Patterns presented in
>> Erich Gamma's book use inheritance. For these two patterns, Factory Method
>> and Template Method, there are better alternative patterns /
implementations.
>
>Please, elaborate.  I'm curious.

So buy the book. I'm not going to summarize all of Gamma's design patterns
for you.

>
>                        *     *     *
>
>> >On the other hand, of course,
>> >even if you have inheritance, you can always choose not to use it.
>> 
>> So, if we keep inheritance, and people use it when they are writing code
>> that they consider less important, do you think they will stop using it
>> when they go to write code that they consider more important?
>
>As you suspect, probably not -- unless there is a sufficiently
>strong external reason to avoid inheritance (e.g. a general principle
>that inheritance is bad for security).

And a bully with a big stick.

>
>But let me point out the flipside: inheritance is not something
>you *can* entirely prevent in E.  Even without any macro sugar for
>inheritance, people can write makers that will set up delegation
>chains and type objects that will respond to queries about what is
>derived from what.  The complementary question to ask, then, is
>whether *not* including any endorsed way of doing lightly-used
>single inheritance will cause several home-grown and different
>kludges for emulating inheritance to spring up and compete with
>each other.  (They might even define their own macros and get
>them wrong...)

Yes, there is no cure for bloodymindedness. The idea is that if you make it
significantly hard enough to do something bad, then people will take the
path of least resistance and do something good. If you additionally provide
some standard libraries that illustrate the righteous path, then you stand
an even better chance of spreading the good word.

Tyler

>P. S. By the way, don't get me wrong: i'm all for simplicity.
>      This position is written at least partly as a devil's
>      advocate to make sure the issue is fully explored.

Yes, you know I know. In these discussions, such "rude surprises" are
necessary.