from preamble:
"Change Set: KCP-0112-FixCanUnderstand Date: 12 December 2003 Author: Nathanael Schaerli
Fixes canUnderstand so that it deals with abstract methods (i.e., subclassResponsibility and shouldNotImplement) in the right way."!
From: n.schaerli@gmx.net
from preamble:
"Change Set: KCP-0112-FixCanUnderstand Date: 12 December 2003 Author: Nathanael Schaerli
Fixes canUnderstand so that it deals with abstract methods (i.e., subclassResponsibility and shouldNotImplement) in the right way."!
Natanael,
Are you sure this is wise? How exactly did you decide what is "the right way"?
How do other dialects implement #canUnderstand:? Are they also "wrong"?
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?
Regards,
Peter van Rooijen
Hi Peter
Good question. Nathanael will certainly reply but just two points:
- if canUnderstand: #foo replies true when a class has a method foo ^ self subclassResponsibility or shouldNotImplement then there is something really wrong in smalltalk....because normally people think that they can call foo on the receiver but this is not the case with those examples.
This means that you will never be able to find code from VW or nay other smalltalk that breaks what nathanael proposes because else the code itself breaks in the other dialects :) funny no! - The usage of canUnderstand: should really be restricted. In fact this is a meta level interface functionality and we should have a task force to carefully evaluate the use in the current image as usage of canUnderstand is 99 % of the time a sign of not so well-designed code.
Thanks for letting me having fun! This was really cool.
Stef
On 12 déc. 03, at 18:05, Peter van Rooijen wrote:
From: n.schaerli@gmx.net
from preamble:
"Change Set: KCP-0112-FixCanUnderstand Date: 12 December 2003 Author: Nathanael Schaerli
Fixes canUnderstand so that it deals with abstract methods (i.e., subclassResponsibility and shouldNotImplement) in the right way."!
Natanael,
Are you sure this is wise? How exactly did you decide what is "the right way"?
How do other dialects implement #canUnderstand:? Are they also "wrong"?
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?
Regards,
Peter van Rooijen
I'm not sure I buy this change either. It seems awfully special-casey...
I mean, the object *does* respond to that message - it does so by returning an error telling you that a subclass should have implemented the method in one case. If the subclass hasn't implemented the method, this is an error that we should see - we shouldn't just silently ignore that method.
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.
I agree with Peter that this might be better as another interface; at very least it demands some debate before inclusion.
Julian
ducasse wrote:
Hi Peter
Good question. Nathanael will certainly reply but just two points:
- if canUnderstand: #foo replies true when a class has a method foo ^ self subclassResponsibility or shouldNotImplement then there is something really wrong in smalltalk....because
normally people think that they can call foo on the receiver but this is not the case with those examples.
This means that you will never be able to find code from VW or nay
other smalltalk that breaks what nathanael proposes because else the code itself breaks in the other dialects :) funny no!
- The usage of canUnderstand: should really be restricted. In fact this is a meta level interface functionality and we should
have a task force to carefully evaluate the use in the current image as usage of canUnderstand is 99 % of the time a sign of not so well-designed code.
Thanks for letting me having fun! This was really cool.
Stef
On 12 déc. 03, at 18:05, Peter van Rooijen wrote:
From: n.schaerli@gmx.net
from preamble:
"Change Set: KCP-0112-FixCanUnderstand Date: 12 December 2003 Author: Nathanael Schaerli
Fixes canUnderstand so that it deals with abstract methods (i.e., subclassResponsibility and shouldNotImplement) in the right way."!
Natanael,
Are you sure this is wise? How exactly did you decide what is "the right way"?
How do other dialects implement #canUnderstand:? Are they also "wrong"?
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?
Regards,
Peter van Rooijen
On 12 déc. 03, at 19:44, Julian Fitzell wrote:
I'm not sure I buy this change either. It seems awfully special-casey...
I mean, the object *does* respond to that message - it does so by returning an error telling you that a subclass should have implemented the method in one case. If the subclass hasn't implemented the method, this is an error that we should see - we shouldn't just silently ignore that method.
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.
I agree with Peter that this might be better as another interface; at very least it demands some debate before inclusion.
That's why I did not harvest it yet and ask marcus to have a look at it. Still I suggest you to look in the image how canUnderstand: is used. It is not used in the case where a subclassResponsibility would be called. I still do not understand why we would like to get an error once we check that the object would really implement a methods.
Stef
Julian
ducasse wrote:
Hi Peter Good question. Nathanael will certainly reply but just two points: - if canUnderstand: #foo replies true when a class has a method foo ^ self subclassResponsibility or shouldNotImplement then there is something really wrong in smalltalk....because normally people think that they can call foo on the receiver but this is not the case with those examples. This means that you will never be able to find code from VW or nay other smalltalk that breaks what nathanael proposes because else the code itself breaks in the other dialects :) funny no! - The usage of canUnderstand: should really be restricted. In fact this is a meta level interface functionality and we should have a task force to carefully evaluate the use in the current image as usage of canUnderstand is 99 % of the time a sign of not so well-designed code. Thanks for letting me having fun! This was really cool. Stef On 12 déc. 03, at 18:05, Peter van Rooijen wrote:
From: n.schaerli@gmx.net
from preamble:
"Change Set: KCP-0112-FixCanUnderstand Date: 12 December 2003 Author: Nathanael Schaerli
Fixes canUnderstand so that it deals with abstract methods (i.e., subclassResponsibility and shouldNotImplement) in the right way."!
Natanael,
Are you sure this is wise? How exactly did you decide what is "the right way"?
How do other dialects implement #canUnderstand:? Are they also "wrong"?
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?
Regards,
Peter van Rooijen
From: "ducasse" ducasse@iam.unibe.ch
On 12 déc. 03, at 19:44, Julian Fitzell wrote:
I'm not sure I buy this change either. It seems awfully special-casey...
I mean, the object *does* respond to that message - it does so by returning an error telling you that a subclass should have implemented the method in one case. If the subclass hasn't implemented the method, this is an error that we should see - we shouldn't just silently ignore that method.
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.
I agree with Peter that this might be better as another interface; at very least it demands some debate before inclusion.
That's why I did not harvest it yet and ask marcus to have a look at it. Still I suggest you to look in the image how canUnderstand: is used. It is not used in the case where a subclassResponsibility would be called. I still do not understand why we would like to get an error once we check that the object would really implement a methods.
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).
If that is indeed a viewpoint we would like to take, then
(someObject class canUnderstand: #someSelector) ifFalse: [someObject someSelector]
would be expected to run into a #doesNotUnderstand:, not a #subclassResponsibility or a #shouldNotImplement or what have you.
If accepted, this would be an argument against the proposed change.
Does the argument make sense?
Regards,
Peter
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
From: "Nathanael Schärli" n.schaerli@gmx.net
Hi guys,
Hi Nathanael!
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.
This bothers me. I'm all for democracy, but quite unconvinced that it should have this type of effect in programming. Especially in a system that is extensible, where alternatives can be made available quite easily to suit various people's tastes.
As an example, consider the method ImageReadWriter>>close:
close "close if you can" (stream respondsTo: #close) ifTrue: [ stream closed ifFalse: [stream close]]
Maybe that is a good example. Let's assume it is not a hack (in the sense of code written just to make the thing not conflict with its adopted context - which it most likely is). Then, what would be a better implementation? What would be "the best" implementation? The "correct" implementation?
I'm going to look at alternatives without studying the system context, just looking at this code and what it communicates to the reader, by itself.
But first some questions:
1) The comment is a bit strange. It doesn't express the same as the method name. The "if you can" part is key. What does the comment actually mean?
2) Could stream possibly be nil? We can't tell from the method itself because UndefinedObject probably doesn't implement close, so this code could be functional (work in practice) if stream is indeed nil.
3) The code expects that any object that responds to close, responds to closed as well. Clearly, the test is an interface/type test. Is there a Smalltalk pattern for interface/type tests? Perhaps the answer is that there is not.
4) The code only sends close when stream is not closed. Is this to avoid unnecessary work? If so, it implies an expectation that the semantics of stream close doesn't handle streams that are closed in a good manner.
Now some code alternatives:
1) close stream ifRespondsPerform: #close
2) close stream ifNotNilDo: [:s | s ifRespondsPerform: #close]
3) close stream ifImplementsPerform: #close "implements means responds and is not a marker method doing something like self error: or self subclassResponsibility or self shouldNotImplement"
4) close stream ifNotNilDo: [:s | s ifImplementsPerform: #close]
5) close stream close
6) close self stream close
7) closeIfYouCan stream ifNotNilDo: [:s | s ifImplementsPerform: #close]
And I could go on (and I didn't even do a closed test ;-)).
Maybe my point is that I do agree with you that considering usage is important:
1) After all the reason for methods existing is them being used. (I say this here even though it isn't true to make a point also: the marker methods don't have that purpose - their purpose is to express type info inside a derivation structure that is based on classes, not types).
2) We should then always keep in mind the question whether we consider the usage pattern desirable.
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!
[I don't agree that the code tests if stream's class understands close. If it did, it would have sent stream class canUnderstand: #close. But it sends stream respondsTo: #close, which means that it recognizes the possibility that stream responds to more than what its class implements as methods, because respondsTo: may have been redefined for stream.]
This is the type/class confusion again. Classic Smalltalk doesn't have a type lattice separated from the class hierarchy. Because of this, we can only add rudimentary/embryonic/partial type info to the system. Should we expect that this half-baked support for types lets us express a lot of reasonable type info in an elegant and consistent way? Perhaps we should simply not expect that.
It seems to me this whole discussion revolves around type/class separation and type/class confusion. The kind of behavior you are seeking has to do with types, but you expect/want regular classes to give it to you. I believe that you will always find conflicting forces if you do that.
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')
I never understood this part of the argument. If we run into a self subclassResponsibility method, then the implementation of the object we sent the message to, is already incorrect, right? Why should we expect to be able to handle it well further down the line?
or declares it to be not appropriate (i.e., by implementing it with the body 'self shouldNotImplement').
This is type-related information stuffed in a class hierarchy. Square peg, round hole. You shouldn't expect it to work elegantly, IMO.
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.
You are indicting Smalltalk for not having a type system like you would want it to have. You are assuming it has it, but it's broken. I say that it doesn't have it, so you shouldn't expect it to work.
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.
Type/class confusion again.
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.
Actually, it is arguable that it is only good style if you accept its limitations.
(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).
I do agree with that.
[Note: personally, I write
methodPattern self halt "subclass responsibility"
because this is portable (subclassResponsibility is not) and halts are resumable on all dialects, so we get the chance to create a solution in the debugger.]
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.
Could you explain again what other code would be broken, and why this code should work?
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.
I don't get it. Especially the "crashing the whole image" part.
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.
If these methods are "wrong" for some reasonable definition of "wrong", then maybe they deserve to be changed?
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!
I basically agree with the goal. I don't agree with your "solution".
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.
:-). I already don't advocate using subclassResponsibility, see above.
b) Change #canUnderstand: (and #respondsTo:) so that the semantics actually corresponds to what 99% of the the *current* users expect when they call it.
Are you saying these (99%) methods are all broken because they expect the wrong semantics?
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.
d) introduce #implements: and #implementsForInstances: which do what you want (exclude pseudo/marker "implementations"), and then let the maintainers of the 99% decide if they want to migrate to those, at a time of their choosing.
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:
I don't find you "logic" at all convincing.
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 obviously expected for decades.
The frequent use of "obviously" and variations, doesn't strengthen your argument, to my mind at least.
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.
That sounds quite reasonable. If that is what you want, and Smalltalk does not offer it in its current state, why don't you *add* it to the language?
So you can write:
abstractMethodPattern self abstract
and
inappropriateMethodPattern self inappropriate
Quite intention revealing, I would think, and it doesn't break anything.
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'.
Yes.
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.
No. The meta-functionality does not concern itself with the question if a method is a regular method or a marker method expressing type information, because the meta-functionality does not deal with types. You *want* it to deal with types to a certain extent, but it does not.
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?
I understand your point. But don't be fooled by the names. Both implementations are variations of self thisMethodShouldNotBeActivatedDuringNonDevelopmentOperation.
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 still don't understand why you feel you need this change. Perhaps I just haven't paid attention well enough.
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'.
Note that you are expecting some kind of fixed/constrained semantics for these methods. That might not be entirely justified.
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!
Indeed, classic Smalltalk is too poor for that. It is not clear to me if you are accepting of that fact or not.
Which is completely paradox to begin with!
Why is that paradoxical? Smalltalk is "too poor" to offer it directly, so any "support" for it that it does have, is a kludge. And kludges can't be expected to scale. That is how I see it.
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.
You are making an awful lot of assumptions about other programmers' intentions. Even though I broadly agree with your goals, I feel the concept of asking for a change to something that has worked for ages, where you could reasonably use the extensibility of the system to achieve what you want, is a dubious proposition.
You could choose to look at this issue in this fashion: You have identified a gap in the meta-protocol. This gives you the opportunity to define new meta-protocol that others might like and wish to see incorporated in their systems. Wouldn't that be nice?
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++.
I agree there. See self abstract and self inappropriate.
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!!
We've covered this. It is the class/type confusion issue. The only way to really cleanly get rid of it is to implement a type lattice separate from the class hierarchy. Which, if I'm not mistaken, is not at all inconsistent with your goals in the Traits proposal. Am I mistaken?
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.
You still haven't made the point that there is an error in a convincing manner, so talking about a "fix" still seems to me to be highly prejudicial.
Regards,
Peter
Peter van Rooijen wrote:
From: "Nathanael Schärli" n.schaerli@gmx.net
[...]
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')
I never understood this part of the argument. If we run into a self subclassResponsibility method, then the implementation of the object we sent the message to, is already incorrect, right? Why should we expect to be able to handle it well further down the line?
[...]
If you ask me, this is the crux of the argument that the rest of the thread is missing. self subclassResponsibility is not really a meta marker on the class - it's an error. It's implemented as:
subclassResponsibility "This message sets up a framework for the behavior of the class' subclasses. Announce that the subclass should have implemented this message."
self error: 'My subclass should have overridden ', thisContext sender methodSelector printString
Human beings can look at and see easily that to avoid that error, they're going to need to define the method in their subclass. But the point of it being an error is that it should never happen. If that method gets called then there is a bug in the system and an error *should* be raised. The fact that that method got called means there is a problem in the system. And *that*, in my opinion, is the semantic meaning the programmer intended when they wrote a method that called #subclassResponsibility in the first place.
If we're going to say that objects don't respond to methods that call these methods then we'd better do the same for #errorNotIndexable, #errorNotIntegerIndex, #errorImproperStore, #caseError, etc... Perhaps we should include methods that call #halt or #error: directly. Hmm - what about ones that don't use any of they methods but create an error class and signal it?
Ok, I'm getting silly but I think it makes a valid point. Just because a method raises an error does not mean that the system should report that the object doesn't respond to that method. And while I understand that this is a special case hack because it only looks for methods with non-conditional errors, it still feels like a special case hack and I don't like that.
When I call #subclassResponsibility I expect an error to be raised because that's what happens. I don't understand why anyone would expect anything else to happen... are you sure existing code really expects methods not to be called if they call #subclassResponsibility? That seems to me like a wholly incorrect assumption on the part of whoever wrote that code.
Julian
On Dec 15, 2003, at 2:58 PM, Julian Fitzell wrote:
If you ask me, this is the crux of the argument that the rest of the thread is missing. self subclassResponsibility is not really a meta marker on the class - it's an error. It's implemented as:
I'd go even futher here:
First, the fact is Smalltalk doesn't really have the notion of abstract methods. I don't see this as a short coming of Smalltalk. Abstract methods are only needed in languages that confuse classes with type, as Peter mentioned earlier. In order to support polymorphism you need to put the method "too high" in the class hierarchy so that the type checker won't complain when it gets sent to the supertype.
Now, a problem that Smalltalk *does* face is that MNU errors are ambiguous. When you get one, the problem might be in either then sender or the receiver. That is, either the message shouldn't have been sent, or the receiver should have understood it. When a programmer calls #subclassResponsibility or #shouldNotImplement, what she's really doing is disambiguating the error.
In the case of #subclassResponsibility, she is saying that the message was correct, and the object should respond to it. The fact that it didn't is an error, and the fix is not to avoid sending the message, but to implement the method. The reverse situation applies to #shouldNotImplement. The programmer is telling us that the message makes no sense to this object and should not have been sent. This clarification is especially handy when the limitations of single inheritence (ie, lack of Traits) cause the object inherit an inappropriate method, which, if activated, would result in an error even more obscure than MNU.
I think #shouldNotImplment is misnamed. It should really be #cannotUnderstand. Conversely, #canUnderstand: is asking whether or not it makes sense to send a certain message to an object. So, for the following code, I think it would be reasonable to get a #subclassResponsibility error, but not a #shouldNotImplement error.
(object canUnderstand: #foo) ifTrue: [object foo[]
Cheers,
Colin
Colin Putney wrote:
On Dec 15, 2003, at 2:58 PM, Julian Fitzell wrote:
If you ask me, this is the crux of the argument that the rest of the thread is missing. self subclassResponsibility is not really a meta marker on the class - it's an error. It's implemented as:
I'd go even futher here:
First, the fact is Smalltalk doesn't really have the notion of abstract methods. I don't see this as a short coming of Smalltalk. Abstract
Nor do I
methods are only needed in languages that confuse classes with type, as Peter mentioned earlier. In order to support polymorphism you need to put the method "too high" in the class hierarchy so that the type checker won't complain when it gets sent to the supertype.
Now, a problem that Smalltalk *does* face is that MNU errors are ambiguous. When you get one, the problem might be in either then sender or the receiver. That is, either the message shouldn't have been sent, or the receiver should have understood it. When a programmer calls #subclassResponsibility or #shouldNotImplement, what she's really doing is disambiguating the error.
This is an interesting way of looking at it and a subtlety I hadn't quite put my finger on.
In the case of #subclassResponsibility, she is saying that the message was correct, and the object should respond to it. The fact that it didn't is an error, and the fix is not to avoid sending the message, but to implement the method. The reverse situation applies to #shouldNotImplement. The programmer is telling us that the message makes no sense to this object and should not have been sent. This clarification is especially handy when the limitations of single inheritence (ie, lack of Traits) cause the object inherit an inappropriate method, which, if activated, would result in an error even more obscure than MNU.
I think #shouldNotImplment is misnamed. It should really be #cannotUnderstand. Conversely, #canUnderstand: is asking whether or not it makes sense to send a certain message to an object. So, for the following code, I think it would be reasonable to get a #subclassResponsibility error, but not a #shouldNotImplement error.
(object canUnderstand: #foo) ifTrue: [object foo[]
I agree that #shouldNotImplement is a strange name (I think #shouldNotBeCalled or something would be better since it *has* implemented the method). But, I'm still not sure #canUnderstand: should be looking at the method source to come up with an answer. I understand the desire for a mechanism that works like that, but using method calls as a marker to anything but the human eye seems pretty fishy to me.
The "right" solution, I suppose, is to get rid of the uses of #shouldNotImplement by using Traits (or whatever), since their very presence smells of a hack in the first place. Adding any behaviour to #canUnderstand: to account for this hack is then, by definition I think, also a hack. Which I guess is possibly ok as long as we accept it and comment it as such. But I think it's better to limit the hack to getting an error when you do something wrong (calling a method that should not ideally even be implemented) rather than pushing it into other methods: the hack will still be a hack but it will be harder to isolate.
For example, someone might write:
true ifTrue: [self shouldNotImplement]
or:
false ifTrue: [self shouldNotImplement]
and we couldn't write code that would actually catch both cases properly. Hack, hack, hackety, hack... :)
Julian
Hi guys!
Julian Fitzell julian@beta4.com wrote: [SNIP]
When I call #subclassResponsibility I expect an error to be raised because that's what happens. I don't understand why anyone would expect anything else to happen... are you sure existing code really expects methods not to be called if they call #subclassResponsibility? That seems to me like a wholly incorrect assumption on the part of whoever wrote that code.
Yes, I have read through most of the postings (phew) and I can at least say that the following scenario seems pretty "normal" to me:
1. I write an abstract class and mark the method #cleanUpTheMess with "self subclassResponsibility" because it really needs to be implemented by the subclasses, and I write a subclass Workshop that implements it.
2. Let's pretend Julian's code holds a bunch of objects that are "potentially cleanable" and Julian decides to check using respondsTo: that the message #cleanUpTheMess can be sent to them. All is well, the ones implementing it get the message sent to them.
3. Some idiot creates a new subclass Kitchen and forgets to implement said method. As it is now this will lead Julian's code to go BAM, he will get a prompt error in his face saying that the method #cleanUpTheMess should be implemented in class Kitchen, but isn't.
But... if we make respondsTo: "smarter" as has been proposed - this mistake will go undiscovered! We will instead end up with messy Kitchens! :)
So at least given this scenario I think I am on Julian's side.
These methods are written like this because if we ever end up in them - then someone has botched it and we want to know about it! It means either that the object is "missing" a method (subclassResponsibility) given the expectations of the sender, or that someone is sending a message to it that they really shouldn't send (shouldNotImplement) - the last case implying that either the sender is just wrong - or the sender is talking to a different object than it expects to be talking to. :)
There are perhaps tons of other subtle issues here (as the very long postings on the subject seem to imply) - but I want to understand the above scenario in the light of the new proposal before going into those. ;)
regards, Göran
Am Montag, 15. Dezember 2003 13:16 schrieb Nathanael Schärli:
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.
Whenever I hear someone say that something is "unarguably" so, it raises the itch in me to argue. :)
Why is it 'unarguably' good style to fill abstract methods with the line "self subclassResponsibility"? In the example of #close for a general stream, is "self subclassResponsibility" an abstract formulation of the semantics of the close operation? I don't think so. What if #close just contained a comment "My subclass should implement this - do nothing"? Arguably, the trouble you have with the semantics of #respondsTo: in dealing with such cases is a consequence of putting something in these abstract methods which doesn't really (semantically) belong there. Instances of the class actually respond to #close, but the response is not meaningful in the given semantic context.
Lothar
"Walk this world with me"
Hello Lothar
Lothar Schenk wrote:
Am Montag, 15. Dezember 2003 13:16 schrieb Nathanael Schärli:
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.
Whenever I hear someone say that something is "unarguably" so, it raises the itch in me to argue. :)
Question:
Why is it 'unarguably' good style to fill abstract methods with the line "self subclassResponsibility"?
Answer: Because this is an idiomatic convention in the Smalltaker subculture to define a method as abstract; see Kent Becks book about Smalltalk idioms.
The point ot Nathanael is that he likes the system to have a way of distinguishing between abstract and non-abstract methods. That's what he means when he writes the following
<citation Nathanael> 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.
</citation Nathanael>
Even if Smalltalk is a relatively good language it does not mean that it is the only one and that it couldn't be cleaned up or enhanced somewhat.
Between 1970 and 1980 every two years a new version of Smalltalk was developed. Now we have 2003. Why not have a little clean-up?
In general OO software engineering circle the distinction between abstract and non-abstract methods is considered to be a valuable distinction. Lothar, I really do not see your point.
Cheers Hannes
Am Montag, 15. Dezember 2003 18:29 schrieb Hannes Hirzel:
Hello Lothar
Hello Hannes
Question:
Why is it 'unarguably' good style to fill abstract methods with the line "self subclassResponsibility"?
Answer: Because this is an idiomatic convention in the Smalltaker subculture to define a method as abstract; see Kent Becks book about Smalltalk idioms.
I know that. But, "because that's the way we do it (always did it)" or "because Kent Beck says so" is not an argument.
The point ot Nathanael is that he likes the system to have a way of distinguishing between abstract and non-abstract methods.
Is there an intrinsic property of a method which can be used to distinguish between an abstract and a non-abstract method?
In general OO software engineering circle the distinction between abstract and non-abstract methods is considered to be a valuable distinction.
I'm not arguing against the usefulness of this distinction. I am, however, arguing that abstract methods and their nonabstract counterparts should express the same basic semantic content.
So, is "self subclassResponsibility" a formulation of the abstract semantics of a given method, e.g. the closing of a stream?
Lothar, I really do not see your point.
I see. :)
Lothar
"Walk this world with me"
Hi Nathanael,
I just wanted to add my opinion to the discussion:
My first reaction was that I am totally in favour of including your changes. I looked at all the senders of #respondsTo:. There are 125 of them in my image. I looked at about 20 of them and *all* of them had the following pattern:
(anObject respondsTo: #initialize) ifTrue: [anObject initialize]
For me this clearly indicates that a developer using #respondsTo: in this way assumes that (s)he can savely call the selector without getting a debugger. I consider the old behaviour to be an outright bug. And it is not problematic to change the semantic of buggy methods, quite on the contrary. I am sure the image will be more robust with the proposed change.
Then I looked at the senders of #canUnderstand:. There are only 14 of them in my image and there are two differen usage patterns:
(anObject class canUnderstand: #asMorph) ifTrue: [anObject asMorph]
The second pattern looks like this:
(aClass canUnderstand: #someSelector) ifFalse: [ self compile: #someSelector]
The first pattern should be rewritten using #respondsTo: anyway. Then only the second pattern remains. And these should not use the new semantic IMO. Because then the marker methods might get recompiled and the marker information is lost. That would be a Very Bad Thing, don't you think so?
So my conclusion: Change the (currently buggy) semantics of #respondsTo:, rewrite the first usage pattern of #canUnderstand: with #respondsTo: and do not change the semantic of #canUnderstand:.
(Another interesting but totally different discussion: I do not like #respondsTo: very much in the first place. I am almost sure some senders could be redesigned so that they do not need it anymore.)
By the way, many thanks for your work! I am really looking forward to a Traits package that I can load in a current Squeak image!
- Bernhard
See http://minnow.cc.gatech.edu/squeak/472 for a summary of the history of this post. If it were up to me I would proceed with Nathaneal's cs. But given that we have not been able to resolve the issue for over a year, I recommend closing this posting group. There is no point in keeping it in BFAV; it just adds to the clutter.
This changeset should be added to squeak. On 5 déc. 04, at 05:18, tomkoenig@mindspring.com wrote:
See http://minnow.cc.gatech.edu/squeak/472 for a summary of the history of this post. If it were up to me I would proceed with Nathaneal's cs. But given that we have not been able to resolve the issue for over a year, I recommend closing this posting group. There is no point in keeping it in BFAV; it just adds to the clutter.
squeak-dev@lists.squeakfoundation.org