[FIX][KCP] KCP-0112-FixCanUnderstand

Nathanael Schärli n.schaerli at gmx.net
Wed Dec 17 12:23:56 UTC 2003


Hi all,

I'm responding here to the post of Richard because it seems the best
foundation.

> With respect to #respondsTo: and #canUnderstand:
> I feel rather cross, because this issue has *already* been discussed. 
> I provided an [ENH] which defined
> 
>     Object>>honestlyRespondsTo:
>     Behavior>>canHonestlyUnderstand:

I did not willingly "ignore" your code and the discussion (and I don't
think it is buggy either). I was simply not aware of it. Sorry!

Nevertheless, I like your suggestion. In fact, this is what I did in the
traits prototype implementation, except that I used slightly different
names:

Object>>reallyRespondsTo:
Behavior>>canReallyUnderstand:

Even though this worked well and is definitely the most uncontroversial
thing to do, I also never felt 100% happy with it. The reason is that
the need for having a method named honestlyRespondsTo: or
reallyRespondsTo: just gives me the feeling that something is wrong with
this protocol (i.e., repsondsTo:, honestlyRespondsTo:, canUnderstand:,
canHonestlyUnderstand:) in the first place. It just doesn't seem very
clean.

> There are 112 senders of #respondsTo: in Squeak 3.6g2; if it is true 
> that "(the human authors of) the vast majority of senders (of 
> #respondsTo:) in the system" didn't understand the semantics of a 
> basic operation which is described extremely clearly in several 
> classic textbooks, this is extremely worrying.  But still, adopting 
> (and correcting, if
> necessary) the
> #honestlyRespondsTo: solution and fixing the broken code
> seems better than changing the semantics of a basic operation.

I can well see your argument regarding compatibility to Smalltalk-80 and
its description in textbooks. If people want to stay compatible to
Smalltalk-80 (even though Squeak is not really compatible anymore
anyway), changing such basic operations is definitely not the right
thing to do. I guess I didn't value this Smalltalk-80 compatibility
concern high enough because it is not important to me.

> I agree 100% that abstract classes and abstract methods are important 
> OO concepts.  I expect anyone who uses them to have made an honest 
> attempt to understand them.  A class which has at least one abstract 
> method is an abstract class.  And one of the basic rules about 
> abstract classes is DON'T CREATE INSTANCES OF ABSTRACT CLASSES.

I see that my example was not very well chosen (I just browsed the
senders of #respondsTo: and took a more or less arbitrary one of them).
However, I don't think that dealing with abstract classes in Smalltalk
is as simple as 'do not create instances of them'. First we need to ask
ourselves the question when a Smalltalk class is abstract. Is it
abstract when it defines or inherits a method with the body 'self
subclassResponsibility' or is it abstract if it somehow sends messages
to 'self' but does not implement these methods?

If you take the latter definition, then all the classes in Squeak are
abstract! However, this would mean that the rule of never instantiating
abstract classes would lead to a system without any instances and
therefore also without any classes (since classes are instances of
metaclasses, which are also abstract).

Since I cannot imagine that this is really what you meant, I suspect
that you mean the former definition. But then I'm wondering what you
think is the difference between the following two scenarios:

- Scenario 1: Class A implements a template method pattern with a
template method #open: that calls 3 hook methods #beforeOpen:, #doOpen:,
and #afterOpen: that are implemented as 'self subclassResponsibility'.
Class B is a subclass of A, but does not implement any of the hook
methods.

- Scenario 2: Class A' is the same as A but does not explicitly declare
the hook methods #beforeOpen:, doOpen:, and #afterOpen:. Class B' is a
subclass of A' with the same methods as B' (i.e., it also does not
implement any of the hook methods).

Well, accoring to the former definition, the class B is abstract whereas
B' is not. Using the rule "never create instances of abstract classes",
this would mean that you should not create an instance of B whereas
creating instances of B' is okay. 

Hmm, I don't agree to this. I really can't see why it would be any worse
to create instances of B than to create instances of B'. It seems that
using an instance of B is as buggy as using one of B'. Therefore, the
classes B and B' seem "equally abstract" to me.

And this is exactly the point I wanted to make. In Squeak, we have this
sort of abstract classes (i.e., classes that issue self-sends to some
methods but do not implement them) everywhere and programmers make
instances of them all the time. And nevertheless, these instances work
fine because the implicitly abstract methods and all the methods that
call such implicitly abstract methods are never called.

However, this immediately falls down as soon as someone tries to be a
good programmer and actually declares such methods as 'self
subclassResponsibility' rather than just leaving them undeclared and
silently hoping that these methods are never called. And the single
reason for this is the fact that the meta-functionality #canUnderstand:
and #respondsTo: cannot distinguish such methods that are declared as
abstract.

As I mentioned in an earlier post, I was trying to be a good programmer
and to explicitly declare all such hook methods. In fact, I even wrote a
quite sophicticated realtime analyzer that detects such undeclared
abstract methods and automatially declares them as 'required'. The
reason for doing this was the belief that being able to see all these
required methods is much better than just having to guess them.
(Finally, somone can still ignore them if he just doesn't care). The
problem is that doing this immediately crashed the whole image, because
there are several places (e.g., in the Morph hierarchy) where
#canUnderstand: or #respondsTo: are then fooled because they think that
the methods that are now explicitly declared as abstract (rather than
being implicitly abstract as before) are actually designed to be called.

Now, it is important to understand that this is not my code and the
reason for proposing my fix is not that I plan to write such code in the
future ;-). Much more, I'm the first to agree that a lot of this code
should not have been written like that in the first place. Nevertheless,
it is a matter of a fact that this code has been written and that the
programmers writing this code used the meta-functionality
#canUnderstand: and #respondsTo: in a way that does not allow other
programmers to explicitly declare the respective methods as abstract. 

And because instances of "abstract classes" (whether explicitly declared
by implementing methods as 'self subclassResponsibility' or by
implicitly self-sending messages that are simply not implememted) are a
reality in Squeak, I think that saying that "instances of abstract
classes should never be created" is generally a wise statement but does
not bring us much closer to solving this problem in reality.

Now, if the majority of Squeakers prefer solving this problem like you
(and I) did earlier, i.e, by introducing a new protocol
#honestlyRespondsTo: and #honestlyCanUnderstand: into the *official*
kernel and then just replacing the majority of all senders of
#respondsTo: and #canUnderstand: so that they call the new protocol
instead, I don't want to stop them. But there are two things about this
that bother me:

First, when we need to change the majority of senders of a method so
that they send another method instead, it seems to me that it may be
better to change the semantics of the original method, and then just
change the few cases were the original semantics was necessary. In
particular, this would decrease the probability that poeple use the
wrong kind of method in the future. Second, it seems to me that the need
for having two protocols with the quite unclear names canUnderstand: and
respondsTo: vs. canHonestlyUnderstand: and honestlyRespondsTo: is a sign
that someting is wrong in the first place.

Thus, I just think that before we officially introduce this new protocol
into the kernel, it would be worth thinking what's really the right
thing to do, and this is precisely the purpose of this discussion.


> Suppose that #respondsTo: is changed so that it answers false when you

> ask about a selector for an abstract method.  (I repeat, this should 
> not be possible, because there shouldn't be any instances that _have_ 
> abstract methods.)
>
> <snip>
>
> As noted repeatedly above, there is, and can be, *NO* fix to
> #respondsTo: which will always give the right answer for code
> which is so blatantly buggy as to provide an object with an 
> abstract method to a receiver which has a use for calling that method.

Well, this is where I think it is important to be clear what we mean
when we talk about abstract classes (and abstract methods, respectively)
in Smalltalk. For you, it seems, using instances of the class B
(scenario 1) is "blatantly buggy" whereas using instances of B'
(scenario 2) is fine. As an illustration, consider the following code:

...
(x respondsTo: #afterOpen:) 
	ifTrue: [x afterOpen: y].
...

Following your argumentation, you seem not to have any problems if this
code is written for instances x of B' (i.e., do not want the code to
break) whereas you consider writing it for instances x of B as buggy
(i.e., you want the code to break as soon as possible).

As I stated above, I do not agree to this distinction and consider
writing such code for B and B' as equally buggy. Therefore, I also do
not agree that it is impossible to fix #respondsTo:. In fact, I think
that fixing it would be as simple as changing the semantics of
#respondsTo: as we suggested. As a consequence, Squeak would then
"correctly" execute such code for instances of B as it has been
executing it for instances of B'.


> Now, I do agree that if a class defines a selector by self 
> shouldNotImplement    
> then an instance of (a concrete subclass of) that class should not be 
> regarded as responding to the selector in question.  That's why I 
> wrote #honestlyRespondsTo:.  A class where you *have* made a conscious

> decision "No I do not support this method" is different form a class 
> where you have decided not to decide yet.

Yes, the case of 'shouldNotImplement' is definitely less controversial
than the one 'subclassResponsibility'. Even if we keep disagreeing on
what exactly should be considered an abstract class in Smalltalk, this
seems to be reason enough to officially add the protocol
#honestlyRespondsTo: and #canHonestlyUnderstand: to the kernel and then
change the majority of the senders of the old protocol to use the new
one. Otherwise, a lot of code breaks if someone uses
'shouldNotImplement' in order to declare that a method is not
appropriate for a certain class.

As I pointed out above, it does not feel like the best solution to me,
but I would of course be willing to accept the decison if this is what
the majority feels like. However, it seems that some Squeakers might not
even like having such a method in the official kernel at all...

Nathanael





More information about the Squeak-dev mailing list