[FIX][KCP] KCP-0112-FixCanUnderstand

Nathanael Schärli n.schaerli at gmx.net
Mon Dec 15 12:16:52 UTC 2003


Hi guys,

> Are you sure this is wise? How exactly did you decide what is "the
> right way"?

I decided that this is the right way because it is the semantics that is
expected by the vast majority of senders in the system. As an example,
consider the method ImageReadWriter>>close:

close
	"close if you can"
	(stream respondsTo: #close) ifTrue: [
			stream closed ifFalse: [stream close]]

This method uses the message #respondsTo: (which is essentially the same
as #canUnderstand:) in order to know whether the class of 'stream'
acually offers the functionality #close and not whether the class of
'stream' declares an abstract or disabled method #close!

Therefore, the code immediately breaks if it is used for a stream class
that either declares the method #close to be abstract (i.e., by
implementing it with the body 'self subclassResponsibility') or declares
it to be not appropriate (i.e., by implementing it with the body 'self
shouldNotImplement').

This is really problematic because especially declaring a method as
abstract is an extremely important concept of OO programming and it is
therefore not acceptable that Smalltalk does not offer a programmer to
do so without breaking the system.

I was running in exactly those problems when I wrote an analyzer that
detects methods that are required in a class (e.g., are self-sent) and
then automatically declares them as abstract by compiling a method with
the body 'self subclassResponsibility'. However, this horribly crashed
the whole image. Why? Because the current semantics of #canUnderstand:
(and #respondsTo:) does not correspond to the semantics that the users
of these selectors expect. 

In fact, there were dozens of places deep in the core of the system
(e.g., in the Morphic framework) that caused a runtime error just
because I was being a good programmer and actually declared abstract
methods as 'subclassResponsibility', which is unarguably a good style of
programming. (It makes the code and the used design patterns (e.g., the
template method pattern) much easier to understand and decreases the
probability for errors in subclasses).


> How do other dialects implement #canUnderstand:? Are they also
> "wrong"?

I don't know. If they do it the same way as it has been done in Squeak,
it is more than likely that also in these dialects, the semantics of
#canUnderstand: and #respondsTo: does not correpsond to what is expected
in practically all the users of these methods. As a consequence, I
assume that also these dialects do not allow a programmer to declare
methods that should be implemented in subclasses as
'subclassResponsibility' without breaking other code.


> If you need the new semantics for something you are working on, why 
> not add another method that does exactly what you want instead of 
> modifying this old-timer?

Our goal is to have a kernel that allows a programmer to explicitly
declare methods that should be implemented in a subclasses (i.e.,
abstract methods) and methods that are not appropriate for a certain
class without crashing the whole image.

Of course, this could also be done by introducing a new method #foo:
rather than changing this old-timers. However, this would mean that we
also had to change 99% of all the callers of these methods so that they
would use #foo: instead. Otherwise, this does not bring us any closer to
our goal of having a language with a kernel that actually allows
declaring abstract methods without nasty side-effects!

Thus, we have three different choices:

a) Leave everything as it is. This means that declaring a method as
abstract or inappropriate for a class has nasty side-effects that break
other code or even crash the image. As a consequence, it is for example
not possible for a proigrammer to consistently declare abstract methods.
In fact, in the current version of Squeak I would even suggest a
programmer never to declare 'subclassResponsibility' method because it
is safer.

b) Change #canUnderstand: (and #respondsTo:) so that the semantics
actually corresponds to what 99% of the the *current* users expect when
they call it.

c) Introduce new methods for #canUnderstand: and #respondsTo: and change
practically all the users of #canUnderstand: and #respondsTo: so that
they now use these new methods instead.

For us, a) is not acceptable because we do not want to have a kernel
that does not allow clean OO programming. When we had to choose between
b) and c) we decided for b) just because #canUnderstand: and
#respondsTo: are such old-timers!! This may sound paradox but it is in
fact logical:

As the Squeak image shows, the vast majority of programmers are used to
using these old-timer methods when they actually should use the new
methods. Therefore, when going for alternative c), it would be just a
question of time until there is new code that inappropriately uses these
methods again! This is not the case in b), because we make the semantics
of these methods consistent with what the majority of Smalltalkers
obviosously expected for decades.


> Obviously this depends on where #canUnderstand: is being called, but
> the appropriate behaviour to me seems to be to check whether the
object 
> responds to the message, not to consider what it is going to do *when*

> it responds to the message - that's a slippery slope that we can't go 
> very far down unless we're going to somehow be able to come to a 
> semantic understanding of all arbitrary code.

We neither need nor want to understand the semantics of arbitrary code.
We just want to offer the programmer a the means to declare a method as
abstract or not appropriate in a way that is compatible and
understandable by the meta-protocol of the language.

The crux of the problem is that Smalltalk does not have *language*
feature for these two things. Instead, Smalltalk suggests an idom, which
is declaring an abstract methods by implementing it with the body 'self
subclassResponsibility' and declaring inappropriate methods by
implementing it with the body 'self shouldNotImplement'. 

And even though this may seem like a simple and good solution at a first
glance, it introduces a lot of problems because this way of declaring
abstract methods is not understood by the meta-protocol of the Smalltalk
language. As a consequence, all the meta-functionality of Smalltalk
(i.e., methods such as #canUnderstand: and #respondsTo:) thinks that
such abstract or disabled methods are regular methods that are
intended/expected to be called. But this is of course the absolute
*opposite* of what the programmer wants to express and it therefore
leads to paradox behavior when such meta-functionality is used. 

Come to think of it: Isn't it paradox if a programmer wants to
explicitly state that a method is not implemented in a class (e.g., by
using 'subclassResponsibility' or 'shouldNotImplement'), and as a
consequence, all the meta-functionality of Squeak thinks that it
actually is implemented?

And this is precisely what we want to fix: We want to make sure that the
meta-functionality of Squeak actually understands the special meaning of
these methods so that the programmer has a way of consistently declaring
abstract and inappropriate methods. This is all, and it does not require
understanding arbitrary code. In fact, it just requires understanding
two idoms (i.e., 'subclassResponsibility' and 'shouldNotImplement').


> I think there is another way of looking at this that might be helpful.

> Let us look at #canUnderstand: as a companion of #doesNotUnderstand:. 
> It doesn't seem so far-fetched to think that "Understand" in both 
> selectors has a related meaning (even though one is on the class and 
> the other on the instance, so there is some inconsistency there).

On a low and technical level, you are right. It is indeed true that
Squeak 'understands' a message that has been declared as abstract or
disabled by the programmer by implementing a method with the body
'shouldNotImplement' or 'subclassResponsibility'. However, this is a
consequence of the unfortunate fact that Smalltalk language is too poor
to express declaring abstract or disabled methods in any other way than
actually implementing them! Which is completely paradox to begin with!

On a higher and conceptual level, writing a method with the body
'shouldNotImplement' or 'subclassResponsibility' is the way a programmer
declares a method as not appropriate or available in a certain class.
And therefore, it makes total sense if such a method is then 'not
understood' by the class. In fact, this is the way it should be because
it is precisely what the programmer intended to achieve.

Since I prefer naming methods according to their conceptual and
higher-level meanig rather than their lower-level implementation (e.g.,
I prefer #addMethod: rather than #insertMethodIntoMethodDictionary:), I
prefer the higher-level view. This is particularly the case since we are
dealing with a higher-level language like Smalltalk and not with C or
C++.

Also, I argue that this higher-level view is the only consistent view
anyway. Just consider that Smalltalkers implement a method with the body
'self shouldNotImplement' to declare that the same method should not be
implemented! Looking at it on a technical level, this does not make any
sense, because it is necessary to implement a method in order to express
that it actually should not be implemented!!

It is only when we look at this on a higher-level that it makes sense.
On this level, we just consider the intention of the programmer, and
this is that he wants to declare that a method is not appropriate for a
certain class. Whether we do this by associating a designated method
body to the selector in the method dictionary, or whether we use
syntactic construct such as in Eiffel (i.e., the 'remove:' clause) does
not matter. 

The only thing that matters is that the programmer knows how to declare
this and that the system behaves in way that is consistent with the
conceptual intention of the programmer. Looking at the existing users of
#canUnderstand: and #respondsTo:, this was obviosuly not the case in
Squeak. And that's why we suggested this fix.

Nathanael




More information about the Squeak-dev mailing list