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