To try to move this issue forward, and keep it in the realm of constructive discussion, I'm going to try to clarify what is going on, and put forward a proposal.
I suspect that some (Richard at least), are feeling like myself and other nay-sayers are simply being obstructionist about a simple issue. But there is a very good reason for the kind of critical discussion that has been going on, and it is actually a good thing. The closer a change comes to the core of a system, the more it should be challenged, to make sure it is worthy. We are not talking about JoeBlowCornerOfTheSystem, but the *Collection* class and protocol, arguably as close as you can get to the heart of Smalltalk. This class and protocol have withstood an enormous amount of use in the real world, and even a very slight change needs to be really, really thought about. I jumped into the conversation only because it seemed to me that the necessary "due diligence" wasn't being done.
I've made my points, but to be constructive and try to move forward, here is how I would reason about this problem and what to do about it. I'm laying it out in painful detail to show the kind of restraint I think is needed when dealing with important code like this.
- it is clear that people do try to write (x removeAll: x), and that it doesn't do what they expect. This seems to happen frequently enough that it would be desirable to at least let people know when this happens, so they can write their code a different way. The currently accepted valid ways to empty a collection are to either create a new collection (usually the fastest way) or say (x removeAll: x copy).
- If we come up with a way to detect this case and generate an error, then the only remaining rationale for extending removeAll: to support the special case of the receiver as an argument would be if it was required by ANSI, or made the library or its specification simpler and cleaner, or fixed an actual bug. I believe that none of these has been demonstrated to be true.
So how would we detect the error? Richard has argued for just putting the check in. I say it belongs in an assertion, even though it is a very fast check, and here is why: every time a programmer thinks of some invariant like this that they want to check, they make a little 'cost' calculation in their head, and if the check is going to have much of a performance cost, they tend to not do the check. Using assertions automatically for all of these kinds of checks eases that difficult cost decision, which encourages people to sprinkle these checks all over their code (a Good Thing). Just because this particular check happens to be very fast does not make it a special case.
So I would recommend the following fixes:
1) Get the assertion mechanism working effectively and efficiently in the standard Squeak release. Make sure its cost in development is minimal, and its cost in production, zero.
2) Just add "self assert: [ otherCollection ~~ self ]." to the beginning of removeAll:.
Very, very simple. (a Good Thing). It prevents this whole issue from being a problem, with no changing of the basic Collection interface or implementation required, and with no performance impact in production (Good Things). It also gives everyone notice that they can now sprinkle their code with assertions (a *really* Good Thing).
Now, to the second, separate matter: is a new #removeAll method needed?
I can see only one good reason for a #removeAll message. That is that a large, tenured collection might need it as a hook to implement a more efficient way of being emptied. Another argument that might be made is that people need a generally easy way of removing all of a collection's elements, but I don't buy that because most of the time, they should just create a new empty collection, which is usually faster, so we don't want to particularly encourage doing #removeAll anyway, since it is usually the slowest way to get the job done.
However, the large collection argument is a good one, I think. The question then becomes, where does this method belong? One could argue that code dealing with such large collections almost always knows a lot more about the collection than just that it is a collection that supports remove:, so adding efficient removeAll methods to specific collection classes could be done without adding it to the Collection protocol itself.
On the other hand, since removeAll is conceptually built using remove:, adding a default implementation to Collection doesn't break any subclass of collection, since it doesn't use any message other than do: and remove:. It would, however, break classes that implement the Collection protocol but which don't inherit from it (ah, for mixins).
So basically, I think this argument comes down to weighing whether the "large collection" argument is good enough to overcome the drawback of breaking existing classes that implement Collection without inheriting from it. I'm not drawing a conclusion here, just trying to delineate the crux of the issue. I personally think removeAll would be a nice addition, but it would break some code out there, and it's not used that often (and shouldn't be), so I'm on the fence about it.
If such a method was added, in Collection it needs to be something really simple. You can do this safely and generally without copying if you write removeAll [ self isEmpty ] whileFalse: [ self remove: self anElement ]
where anElement can be the following private method (it can be made public, and is in Strongtalk, but that's another issue): anElement self do: [ :el | ^el ]. self error: 'Collection empty; no element to return'.
Unfortunately, without an inlining compiler, anElement is not very efficient in Squeak right now, although with inlining it could be more efficient than copying. On a 1000 element Set of consecutive integers, the above implementation is a bit slower than (self removeAll: self copy), so maybe the copying version should be used for now instead, if removeAll is to be added (although anElement is a handy method to have around, in its own right). On the other hand, much more efficient implementations of anElement can easily be written for important subclasses, and then removeAll can be done efficiently without copying.
So if there is any overall message here, it is: be very careful with code in classes like this, and show great restraint by making changes as minimal as possible. The code in Collection is simple, but not simple-minded. Don't underestimate the subtlety of why these simple-seeming methods are the way they are.
Cheers, Dave
Hi all!
(Darn, just can't keep my fingers off this thread... :-)
Just to make sure though - not anything I say below is meant to be offensive in any way. If something sounds annoying then I apologize and blame the fact that english is not my native language.
"David Griswold" David.Griswold@acm.org wrote:
To try to move this issue forward, and keep it in the realm of constructive discussion, I'm going to try to clarify what is going on, and put forward a proposal.
I suspect that some (Richard at least), are feeling like myself and other nay-sayers are simply being obstructionist about a simple issue. But there is a very good reason for the kind of critical discussion that has been going on, and it is actually a good thing. The closer a change comes to the core of a system, the more it should be challenged, to make sure it is worthy. We are not talking about JoeBlowCornerOfTheSystem, but the *Collection* class and protocol, arguably as close as you can get to the heart of Smalltalk. This class and protocol have withstood an enormous amount of use in the real world, and even a very slight change needs to be really, really thought about. I jumped into the
Yep, but sometimes you really wonder... Some of the bugs you can find in there (mostly stuff being inherited but not really working well for subclasses) feels quite elementary so I am not sure these classes "have stood the test of time", I rather think it has to do with people being "afraid" to fix them. I wonder what the original authors would say, who wrote these classes in the beginning, was it Dan?
conversation only because it seemed to me that the necessary "due diligence" wasn't being done.
Good.
I've made my points, but to be constructive and try to move forward, here is how I would reason about this problem and what to do about it. I'm laying it out in painful detail to show the kind of restraint I think is needed when dealing with important code like this.
- it is clear that people do try to write (x removeAll: x), and that it
doesn't do what they expect. This seems to happen frequently enough that it would be desirable to at least let people know when this happens, so they can write their code a different way. The currently accepted valid ways to empty a collection are to either create a new collection (usually the fastest way) or say (x removeAll: x copy).
A few remarks: - The situation can occur without it being written "x removeAll: x" intentionally by the developer. Rather "x removeAll: (some-funky-expression-that-just-happens-to-evaluate-to-x)". - Creating a new collection does not retain identity. - "x removeAll: x copy" is really IMHO much more silly than "x removeAll". It demands that you are aware that "x removeAll: x" doesn't work and also that you realize that just sending in a copy would do the trick. Not at all obvious, but finding a method called "removeAll" is very obvious.
- If we come up with a way to detect this case and generate an error,
then the only remaining rationale for extending removeAll: to support the special case of the receiver as an argument would be if it was required by ANSI, or made the library or its specification simpler and cleaner, or fixed an actual bug. I believe that none of these has been demonstrated to be true.
Why would that be "the only remaining rationale"? Honestly, I am not joking? A good reason IMHO is to improve the class. Squeak is not bound by ANSI. Sure, being ANSI compliant (which it isn't) is a good thing, but NOT if it stops us from improvement.
If we see a "developer trap" (=a good possibility of making mistakes), which I surely would call this, then we should try to fix it.
(It's quite exactly the same problem as using removeAllSuchThat: on an ArrayedCollection.
'abc' removeAllSuchThat: [:c | c = $d ]
The code runs just fine and thus pretends to remove all $d's from the receiver, but if there would be a $d in there it will blow. It's a developer trap. (Richard posted a simple FIX for this one))
And a fix can be anything from simply adding a comment, signalling or simply making the darn code work. In this case it seems to me that making it work would be nice. :-)
So how would we detect the error? Richard has argued for just putting the check in. I say it belongs in an assertion, even though it is a very fast check, and here is why: every time a programmer thinks of some invariant like this that they want to check, they make a little 'cost' calculation in their head, and if the check is going to have much of a performance cost, they tend to not do the check. Using assertions automatically for all of these kinds of checks eases that difficult cost decision, which encourages people to sprinkle these checks all over their code (a Good Thing). Just because this particular check happens to be very fast does not make it a special case.
So I would recommend the following fixes:
- Get the assertion mechanism working effectively and efficiently in
the standard Squeak release. Make sure its cost in development is minimal, and its cost in production, zero.
- Just add "self assert: [ otherCollection ~~ self ]." to the beginning
of removeAll:.
Very, very simple. (a Good Thing). It prevents this whole issue from being a problem, with no changing of the basic Collection interface or implementation required, and with no performance impact in production (Good Things). It also gives everyone notice that they can now sprinkle their code with assertions (a *really* Good Thing).
I agree with almost everything you said here about asserts except for the fact that I would not do an assert in this case and instead call removeAll on self and thus make the code work. :-) What is your argument for not doing that? (an honest question)
Now, to the second, separate matter: is a new #removeAll method needed?
I can see only one good reason for a #removeAll message. That is that a large, tenured collection might need it as a hook to implement a more efficient way of being emptied. Another argument that might be made is that people need a generally easy way of removing all of a collection's elements, but I don't buy that because most of the time, they should just create a new empty collection, which is usually faster, so we don't
Identity is not retained. Sure, it would work most of the time but not *all* the time.
want to particularly encourage doing #removeAll anyway, since it is usually the slowest way to get the job done.
Nope. I would say that my proposed implementation of #removeAll in OrderedCollection is faster than creating new OCs. And we all know OCs are the workhorses. Hey, I can back that up with numbers! :-)
Time millisecondsToRun: [100000 timesRepeat: [OrderedCollection newW]] -> 562 561 536
| oc | oc _ OrderedCollection new. Time millisecondsToRun: [100000 timesRepeat: [oc removeAll]] -> 365 375 368
using implementation as: removeAll "Remove all elements in the receiver." self setCollection: ( Array new: 10 )
And when optimizing further as: removeAll "Remove all elements in the receiver." array _ Array new: 10. firstIndex _ 3. lastIndex _ 2
I got: 191 179 177 187
But then I couldn't resist tweaking "OrderedCollection>>new" in the same manner and then got:
Time millisecondsToRun: [100000 timesRepeat: [OrderedCollection newW]] -> 321 327 332 317
But then I checked with "Smalltalk macroBenchmarks" and it only gave 4% on benchmark2, nothing much on the others: Before: #(19025 114834 42563 20877 0 11114 8115) After: #(19305 110163 42722 20594 0 11051 8124) (I haven't checked really if any of these benchmarks creates many OCs. Perhaps it was just noise.)
Anyway it is WAY faster and creates less garbage! And I mean, of course - "OrderedCollection new" does the same thing but more stuff too so obviously #removeAll should be faster. By the way, "root table overflow" - what is that? I got those when doing some other benchmarks.
However, the large collection argument is a good one, I think. The question then becomes, where does this method belong? One could argue that code dealing with such large collections almost always knows a lot more about the collection than just that it is a collection that supports remove:, so adding efficient removeAll methods to specific collection classes could be done without adding it to the Collection protocol itself.
But why? It is such a basic message that I was actually perplexed to see it wasn't in there already. But instead I find rather exotic messages like #removeAllSuchThat: ...
On the other hand, since removeAll is conceptually built using remove:, adding a default implementation to Collection doesn't break any subclass of collection, since it doesn't use any message other than do: and remove:. It would, however, break classes that implement the Collection protocol but which don't inherit from it (ah, for mixins).
So basically, I think this argument comes down to weighing whether the "large collection" argument is good enough to overcome the drawback of breaking existing classes that implement Collection without inheriting from it. I'm not drawing a conclusion here, just trying to delineate the crux of the issue. I personally think removeAll would be a nice addition, but it would break some code out there, and it's not used that often (and shouldn't be), so I'm on the fence about it.
And again, why shouldn't it be used? And to be so overly defensive of adding (note, we are not changing an existing method, we are simply *adding* one) a method seems very counter productive to me. It would more or less totally hinder any evolution at all in the Collection hierarchy...
If such a method was added, in Collection it needs to be something really simple. You can do this safely and generally without copying if you write removeAll [ self isEmpty ] whileFalse: [ self remove: self anElement ]
where anElement can be the following private method (it can be made public, and is in Strongtalk, but that's another issue): anElement self do: [ :el | ^el ]. self error: 'Collection empty; no element to return'.
Unfortunately, without an inlining compiler, anElement is not very efficient in Squeak right now, although with inlining it could be more efficient than copying. On a 1000 element Set of consecutive integers, the above implementation is a bit slower than (self removeAll: self copy), so maybe the copying version should be used for now instead, if removeAll is to be added (although anElement is a handy method to have around, in its own right). On the other hand, much more efficient implementations of anElement can easily be written for important subclasses, and then removeAll can be done efficiently without copying.
Sure, this base implementation looks good to me. Or very simply as you advised developers to do it "manually":
removeAll "Remove all my elements. We use a copy so that #removeAll: will work without stepping on itself. Subclasses typically reimplement this method more efficiently like for example in OrderedCollection." self removeAll: self copy
If you want simplicity, there you go! (Note: The comment could surely be improved)
So if there is any overall message here, it is: be very careful with code in classes like this, and show great restraint by making changes as minimal as possible. The code in Collection is simple, but not simple-minded. Don't underestimate the subtlety of why these simple-seeming methods are the way they are.
I agree but would like to add: "...but don't think they can't be improved. We are all just humans, even Dan Ingalls." :-)
Cheers, Dave
Cheers, Göran
On Wednesday, August 28, 2002, at 06:12 AM, goran.hultgren@bluefish.se wrote:
- it is clear that people do try to write (x removeAll: x), and that
it doesn't do what they expect. This seems to happen frequently enough that it would be desirable to at least let people know when this happens, so they can write their code a different way. The currently accepted valid ways to empty a collection are to either create a new collection (usually the fastest way) or say (x removeAll: x copy).
A few remarks:
- The situation can occur without it being written "x removeAll: x"
intentionally by the developer. Rather "x removeAll: (some-funky-expression-that-just-happens-to-evaluate-to-x)".
This is handled by the assertion proposal.
- Creating a new collection does not retain identity.
Yes, this is the reason why it is valuable to have a method to remove all elements in some cases. It does not suggest that an implicit iteration method should break Smalltalk's "don't change the iterating collection" semantics rule for just a few special cases.
- "x removeAll: x copy" is really IMHO much more silly than "x
removeAll". It demands that you are aware that "x removeAll: x" doesn't work and also that you realize that just sending in a copy would do the trick. Not at all obvious, but finding a method called "removeAll" is very obvious.
Hardly. It is an idiom of the language, and those familiar with most collection iterations do this sort of thing routinely.
- If we come up with a way to detect this case and generate an error,
then the only remaining rationale for extending removeAll: to support the special case of the receiver as an argument would be if it was required by ANSI, or made the library or its specification simpler and cleaner, or fixed an actual bug. I believe that none of these has been demonstrated to be true.
Why would that be "the only remaining rationale"? Honestly, I am not joking? A good reason IMHO is to improve the class. Squeak is not bound by ANSI. Sure, being ANSI compliant (which it isn't) is a good thing, but NOT if it stops us from improvement.
I think David is saying that he doesn't see any requirements for improvement, once ad hoc "would-be-nice-creeping-featuritis" is removed from the notion of an "improvement." There are a kazillion ways to provide the functionality you require.
If we see a "developer trap" (=a good possibility of making mistakes), which I surely would call this, then we should try to fix it.
David's assertion proposal addresses this without changing the semantics.
(It's quite exactly the same problem as using removeAllSuchThat: on an ArrayedCollection.
'abc' removeAllSuchThat: [:c | c = $d ]
The problem here is not that it misbehaves for $a, but that it behaves at all for $d. Assertions would solve the problem here as well. (It is an awkward property of the Collection hierarchy as is that #remove is defined for immutable Collections).
I agree with almost everything you said here about asserts except for the fact that I would not do an assert in this case and instead call removeAll on self and thus make the code work. :-) What is your argument for not doing that? (an honest question)
Because it changes semantics by performing an implicit iteration on a collection that changes during the operation. This is undefined in most Smalltalks (and as many have persuasively argued, this is as it should be). By encouraging these special case expectations, we step into the land of the dangerous, handling only one of a host of possible cases. The iterand, implicit or otherwise, should not be modified during such a loop.
However, the large collection argument is a good one, I think. The question then becomes, where does this method belong? One could argue that code dealing with such large collections almost always knows a lot more about the collection than just that it is a collection that supports remove:, so adding efficient removeAll methods to specific collection classes could be done without adding it to the Collection protocol itself.
But why? It is such a basic message that I was actually perplexed to see it wasn't in there already. But instead I find rather exotic messages like #removeAllSuchThat: ...
I have used the latter with some frequency, in fact, and haven't really had an application where i desired a #removeAll. In practice, I never gave a second thought but to use the #removeAll: self copy solution.
And again, why shouldn't it be used? And to be so overly defensive of adding (note, we are not changing an existing method, we are simply *adding* one) a method seems very counter productive to me. It would more or less totally hinder any evolution at all in the Collection hierarchy...
Many of David's "us" are of the view that we are "fixing" something that isn't broken. Evolution of Collection is good. Not all of "us" see this as even neutral.
Hi all!
Quoting "Andrew C. Greenberg" werdna@mucow.com:
On Wednesday, August 28, 2002, at 06:12 AM, goran.hultgren@bluefish.se wrote:
- it is clear that people do try to write (x removeAll: x), and that
it doesn't do what they expect. This seems to happen frequently enough that it would be desirable to at least let people know when this happens, so they can write their code a different way. The
currently
accepted valid ways to empty a collection are to either create a new collection (usually the fastest way) or say (x removeAll: x copy).
A few remarks:
- The situation can occur without it being written "x removeAll: x"
intentionally by the developer. Rather "x removeAll: (some-funky-expression-that-just-happens-to-evaluate-to-x)".
This is handled by the assertion proposal.
Yes, I know. I was just trying to emphasize the fact that the situation can occur without having coded it explicitly. A developer typing "x removeAll: x" would probably reflect on the fact that it could mean trouble. But as I said, the situation could be "hidden" in the code.
- Creating a new collection does not retain identity.
Yes, this is the reason why it is valuable to have a method to remove all elements in some cases. It does not suggest that an implicit iteration method should break Smalltalk's "don't change the iterating collection" semantics rule for just a few special cases.
Excuse me for my ignorance in this area, but:
1. I (and a newbie I guess) wouldn't consider removeAll: to be an iteration operation. That to me seems to be an implementation thing. 2. Where is this "semantics rule" you mention documented? I haven't read many Smalltalk books...
- "x removeAll: x copy" is really IMHO much more silly than "x
removeAll". It demands that you are aware that "x removeAll: x"
doesn't
work and also that you realize that just sending in a copy would do
the
trick. Not at all obvious, but finding a method called "removeAll" is very obvious.
Hardly. It is an idiom of the language, and those familiar with most collection iterations do this sort of thing routinely.
Ok, I have been coding Smalltalk for many years and not until this thread popped up had I really reflected on the fact that "x removeAll: x copy" would be the "obvious" way of emptying a collection. I do not do this routinely. And you can not convince me that a newbie would "guess" this.
- If we come up with a way to detect this case and generate an
error,
then the only remaining rationale for extending removeAll: to
support
the special case of the receiver as an argument would be if it was required by ANSI, or made the library or its specification simpler
and
cleaner, or fixed an actual bug. I believe that none of these has been demonstrated to be true.
Why would that be "the only remaining rationale"? Honestly, I am not joking? A good reason IMHO is to improve the class. Squeak is not bound by ANSI. Sure, being ANSI compliant (which it isn't) is a good thing, but NOT
if
it stops us from improvement.
I think David is saying that he doesn't see any requirements for improvement, once ad hoc "would-be-nice-creeping-featuritis" is removed from the notion of an "improvement." There are a kazillion ways to provide the functionality you require.
I must say that I am on Richard side in this. We are not talking "featuritis", we are fixing buggy code. Noone can convince me that:
|oc| o := OrderedCollection with: 1 with: 2. o removeAll: o. o
Should give the result it does. NO WAY. An assert (as I gather you favour) is much better than the current situation, but I still can't really understand why we just couldn't "repair" removeAll: so that it works as expected. And I can't really see what "iteration semantics" has got to do with it...
If we see a "developer trap" (=a good possibility of making
mistakes),
which I surely would call this, then we should try to fix it.
David's assertion proposal addresses this without changing the semantics.
So the semantics you keep mentioning, what is that? I guess we are talking about "Do not change the collection during an iteration over the same, it is not defined." (or something like that)
And where am I iterating when I type: "x removeAll: x"? I have no idea what x will do to accomplish the goal of removing all those elements I am sending in - I could guess there might be an iteration in there somewhere but why should I need to know that?
(It's quite exactly the same problem as using removeAllSuchThat: on
an
ArrayedCollection.
'abc' removeAllSuchThat: [:c | c = $d ]
The problem here is not that it misbehaves for $a, but that it behaves at all for $d. Assertions would solve the problem here as well. (It is an awkward property of the Collection hierarchy as is that #remove is defined for immutable Collections).
Sure. It was a pretty far fetched comparison, I was trying to show that methods failing "silently" because of unexpected input is simply buggy IMHO.
I agree with almost everything you said here about asserts except for the fact that I would not do an assert in this case and instead call removeAll on self and thus make the code work. :-) What is your argument for not doing that? (an honest question)
Because it changes semantics by performing an implicit iteration on a collection that changes during the operation. This is undefined in
Hmmm. I can't see where the "implicit iteration" takes place. With my proposed changes (check for identity calls #removeAll instead) no iteration would take place - the collection would simply realize it should empty itself and just does that.
And by the way, what do you think of removeAllSuchThat: ? It also changes the underlying colllection during the operation. But it uses a copy to get away with it... Perhaps we should then remove that method according to these arguments? Or perhaps I misunderstood it.
most Smalltalks (and as many have persuasively argued, this is as it should be). By encouraging these special case expectations, we step into the land of the dangerous, handling only one of a host of possible cases. The iterand, implicit or otherwise, should not be modified during such a loop.
I understand what you are saying, but I don't agree that #removeAll: should be considered to be an "iteration method".
However, the large collection argument is a good one, I think. The question then becomes, where does this method belong? One could
argue
that code dealing with such large collections almost always knows a lot more about the collection than just that it is a collection that supports remove:, so adding efficient removeAll methods to specific collection classes could be done without adding it to the Collection protocol itself.
But why? It is such a basic message that I was actually perplexed to see it wasn't in there already. But instead I find rather exotic messages like #removeAllSuchThat:
...
I have used the latter with some frequency, in fact, and haven't really
had an application where i desired a #removeAll. In practice, I never gave a second thought but to use the #removeAll: self copy solution.
But surely you agree that "x removeAll" is much more precise and obvious than coming up with "x removeAll: x copy"? I mean, just standing there in front of a newbie Smalltalker and saying "No, there is no message for emptying a collection. Instead you write 'x removeAll: x copy'. Isn't that obvious?" Nah. Don't buy it.
And again, why shouldn't it be used? And to be so overly defensive of adding (note, we are not changing an existing method, we are simply *adding* one) a method seems very counter productive to me. It would more or less totally hinder any evolution at all in the Collection hierarchy...
Many of David's "us" are of the view that we are "fixing" something that isn't broken. Evolution of Collection is good. Not all of "us" see this as even neutral.
I didn't understand the last sentence.
regards, Göran
PS. I saw the commit emails, did you get the RePlugin up there alright? DS
Göran Hultgren, goran.hultgren@bluefish.se GSM: +46 70 3933950, http://www.bluefish.se "Department of Redundancy department." -- ThinkGeek
Just to add a little fuel to the fire :)....
On Wed, Aug 28, 2002 at 02:24:49PM -0400, Andrew C. Greenberg wrote:
On Wednesday, August 28, 2002, at 06:12 AM, goran.hultgren@bluefish.se wrote:
[snip]
- Creating a new collection does not retain identity.
Yes, this is the reason why it is valuable to have a method to remove all elements in some cases. It does not suggest that an implicit iteration method should break Smalltalk's "don't change the iterating collection" semantics rule for just a few special cases.
Think about the words "implicit iteration" with respect to "removeAll:".
I'm a newcomer to squeak, but I've been programming for a living since the mid seventies. When I see "removeAll:" the only way I can get to "iteration" is to violate the modularity of the "removeAll:" method.
- If I look inside the method and see the iteration and then make a decision based on knowing "removeAll:" iterates then I've thrown modularity out the window.
- If I practice good modularity and don't look inside the method, and in fact deliberately "forget" anything about how "removeAll:" is implemented, then I can't make any argument about what it should do based on it "implicitly iterating".
If the method was named "iterativelyRemoveAll:" then I could buy your argument :). Since it isn't, modularity says not only that we shouldn't know that "removeAll:" iterates, but that we should make no effort to know and that we should have no expectation one way or the other that it does or does not iterate.
squeak-dev@lists.squeakfoundation.org