"Andrew C. Greenberg" werdna@mucow.com wrote: *** The iterand of a #do: on a collection, implicitly or otherwise, is undefined whenever the structure and contents of the collection is not invariant through the iteration.
That's all very well, but the *CALLER* of #removeAll: is NOT DOING ANY ITERATION. What gets iterated over, when, and how, is the responsibility of the implementation of #removeAll:.
I have shown several implementations of #removeAll: where modification of the receiver just plain doesn't happen during whatever iteration takes place.
I fully and completely agree that when a programmer writes an iteration, *THAT* programmer is responsible for making sure that the collection is not changed. But when someone calls #addAll:, they are not writing an iteration. It was the author of the #addAll: implementation who wrote the iteration, and it was the author of the #addAll: implementation who had the responsibility for making sure that it either worked or was documented as not working.
This is a bedrock principle of the Collection hierarchy's design, and seems to me a sound and reasonable basis for proceeding.
I am in complete agreement with you.
THAT is why we know #removeAll: is broken.
To the extent that I have read what Richard has argued,
I would hope that a responsible arguer would have read all of it.
I am not convinced that #removeAll: is or should be defined in the case of self.
In IBM Smalltalk, I'm told that it _is_. Do you wish to argue that IBM Smalltalk should be changed so that it is not?
> And it really is very much an exaggeration to say that > "x removeAll: x copy" is an "accepted way" to empty a collection. > It is a way. It is a good way. But practically nobody knows about it. > It's what people do when they burn their fingers on "x removeAll: x". Just about everybody seems to know about it, IMHO, even late-comers to the language such as myself.
You have done a survey of all Smalltalk programmers, have you?
If this were true, the thread would never have got started. It _certainly_ isn't mentioned in _any_ of the Smalltalk textbooks I've checked. Surely if it's as well known as all that, _one_ of them would have seen fit to mention it?
I have discussed this issue with several of the staff in this department, including the head of department and the person responsible for 2nd year data structures. I have asked
"Given an abstract data type T that is some kind of container, with an operation remove_all_elements_of(x: T, y: T) explained as 'remove every element of y from x' what would you expect remove_all_elements_of(x, x) to do?"
The unanimous answer was "make x empty".
Ralph Johnson wrote a nice little piece I read early on in my Smalltalking life about common Smalltalk bugs, and the "copy trick" was clearly pointed out there. How nice for you. Tell me where to find it and I'll make sure my students see it.
> I have also explained repeatedly not only _that_ the change makes the > specification simpler and cleaner, but _how_ it makes the specification > simpler and cleaner. Richard certainly has SAID that he feels it makes the spec simpler and cleaner -- not all of us were satisfied either at the sufficiency or correctness of his "explanation." Merely stating, ipse dixit, that something is so certainly does not evidence that it is so. This makes me very angry. I didn't just SAY that I "feel" it makes the spec simpler, I wrote out in full what the specification _is_ right now and what the specification would be if the bug were fixed. I am not going to do this again because this has already taken too much of my time. No reasonable person could call comparing and contrasting the two versions of the specifications "merely stating that something is so."
> Rational/critical debate at least requires a rebuttal of these points, > not a flat denial that no demonstration was ever made. Pot. Kettle. Black. Rational/critical debate at least requires acknowledgment that others have made more than a flat denial when, in fact, they have undertaken to make arguments on the merits.
No, once again you have made a FLAT DENIAL that I demonstrated that the specification is simpler when the bug is fixed.
I have not found Ralph and David guilty of empty gainsay, though I *DO* find Richard's present "claim of victory by default" (but not his earlier arguments) to be precisely that. I have made no such claim. Indeed, I am fighting desperately for reason and safety in programming precisely because I do *not* feel that I have any kind of victory. It is not fair and not reasonable to attack me for 'empty gainsay' for something I have not said or implied.
I am now far too upset to read the rest of the message.
To the people who have supported me in this, thank you.
Especial thanks to the person who pointed out that x removeAll: x *does* work for OrderedCollections in IBM Smalltalk, so that the nay-sayers are fighting to preserve a bug that at least one commercial- quality Smalltalk does not share. (Perhaps _because_ it is commercial quality?)
To the people who have been watching quietly, thank you for your bandwidth. I hope we have all learned something.
To the others, well, thank you for provoking me to probe the issue.
On Wednesday, August 28, 2002, at 10:54 PM, Richard A. O'Keefe wrote:
I fully and completely agree that when a programmer writes an iteration, *THAT* programmer is responsible for making sure that the collection is not changed. But when someone calls #addAll:, they are not writing an iteration. It was the author of the #addAll: implementation who wrote the iteration, and it was the author of the #addAll: implementation who had the responsibility for making sure that it either worked or was documented as not working.
Here is the crux of the argument. In my view, the iteration is implied and, moreover, is impossible to define meaningfully without using some language of iteration, directly or indirectly. Whether the iterand is a receiver, parameter or the result of an expression does not, to me, seem relevant. So, too, it seems that many Smalltalk illuminati agree. Richard does not.
But at least he agrees with the first principle. That is a good basis for proceeding.
Hi all!
Ok, guys. Let's try to calm down and try to resolve this "storm in a pond" (or whatever you say in english). I really like reading all the arguments from all parties involved so far and I really think everyone who has voiced an opinion have had good points.
Sometimes when I read Andrew I lean over to his side, but then reading Richard (who really follow through the rhetoric with such detailed logical reasoning and research it almost hurts my head :-) I lean over to his side.
For anyone just jumping in here this is what I *think* is the current "score":
- We all agree on that Collection>>removeAll: is currently in need of *something*. - I *think* we all agree on that adding a Collection>>removeAll would be a nice addition, given a nice base implementation to fall back on and some concrete implementations in subclasses. - Some parties (including David Griswold and Andrew Greenberg and others I presume) want us to simply add a check in the beginning of removeAll: to see if the argument is == self and then signal an error. - Some parties (including Richard O'Keefe and myself and others I presume) would like to make this case instead "work". That would simply translate into emptying the collection. - What the ANSI standard says is not clear. We do know however thanks to Allen Wirfs-Brock that the committe overlooked the case in question and would probably (if given chance to correct it) opt to call it "undefined". IMHO these two things together makes it uninteresting to talk about ANSI in this case, and even if it was interesting Squeak is by no means bound by ANSI anyway. - Richard has proposed several variants of making it work including using #copy. One variant I myself like is to simply call "self removeAll" in this case and let that method deal with it as it please. - We do know that there are at least two other Smalltalks behaving differently so there is no given way to operate in order to be "compatible" (In VW it fails, in IBM Smalltalk it works). - Richard together with Jan Hylands has done some really interesting archeological findings indicating that removeAll: indeed did work earlier due to differences in #removeIndex:! And more efficiently too, if I am not mistaken.
So the questions are (if anyone has forgotten):
1. Should we simply signal error or make "x removeAll: x" work by making x empty? 2. Should we add a Collection>>removeAll with a base implementation and suitable subclass implementations?
Personal current opinion:
1. Andrew had some nice arguments there for a while about "semantic rules" on not changing operands during operation etc but in the end I got swayed over back to Richard's side, *especially* after the recent archeological findings, but also mainly because the "semantic rules" seemingly to me should only apply when talking about iteration and even though one way of implementing removeAll: is through a simple iteration - that is not the only way to do it and the iteration is *inside* removeAll: so it should not be a problem of the caller.
2. Adding removeAll is a good thing IMHO. Can't possibly see why having to write "x removeAll: x copy" would be better - it can't be efficiently reimplemented in subclasses and it is an obscure way of doing it.
As always, noone replies to my postings but I keep hammering them in there... ;-)
regards, Göran
goran.hultgren@bluefish.se wrote:
Hi all!
Ok, guys. Let's try to calm down and try to resolve this "storm in a pond" (or whatever you say in english). I really like reading all the arguments from all parties involved so far and I really think everyone who has voiced an opinion have had good points.
Sometimes when I read Andrew I lean over to his side, but then reading Richard (who really follow through the rhetoric with such detailed logical reasoning and research it almost hurts my head :-) I lean over to his side.
For anyone just jumping in here this is what I *think* is the current "score":
- We all agree on that Collection>>removeAll: is currently in need of
*something*.
- I *think* we all agree on that adding a Collection>>removeAll would be
a nice addition, given a nice base implementation to fall back on and some concrete implementations in subclasses.
- Some parties (including David Griswold and Andrew Greenberg and others
I presume) want us to simply add a check in the beginning of removeAll: to see if the argument is == self and then signal an error.
- Some parties (including Richard O'Keefe and myself and others I
presume) would like to make this case instead "work". That would simply translate into emptying the collection.
- What the ANSI standard says is not clear. We do know however thanks to
Allen Wirfs-Brock that the committe overlooked the case in question and would probably (if given chance to correct it) opt to call it "undefined". IMHO these two things together makes it uninteresting to talk about ANSI in this case, and even if it was interesting Squeak is by no means bound by ANSI anyway.
- Richard has proposed several variants of making it work including
using #copy. One variant I myself like is to simply call "self removeAll" in this case and let that method deal with it as it please.
- We do know that there are at least two other Smalltalks behaving
differently so there is no given way to operate in order to be "compatible" (In VW it fails, in IBM Smalltalk it works).
- Richard together with Jan Hylands has done some really interesting
archeological findings indicating that removeAll: indeed did work earlier due to differences in #removeIndex:! And more efficiently too, if I am not mistaken.
So the questions are (if anyone has forgotten):
- Should we simply signal error or make "x removeAll: x" work by making
x empty? 2. Should we add a Collection>>removeAll with a base implementation and suitable subclass implementations?
Yes and yes. These are simple no brainers. If/when you have the more powerful version that actually works and doesn't freek the performance gurus out, then you can replace 1 with that.
Personal current opinion:
- Andrew had some nice arguments there for a while about "semantic
rules" on not changing operands during operation etc but in the end I got swayed over back to Richard's side, *especially* after the recent archeological findings, but also mainly because the "semantic rules" seemingly to me should only apply when talking about iteration and even though one way of implementing removeAll: is through a simple iteration
- that is not the only way to do it and the iteration is *inside*
removeAll: so it should not be a problem of the caller.
- Adding removeAll is a good thing IMHO. Can't possibly see why having
to write "x removeAll: x copy" would be better - it can't be efficiently reimplemented in subclasses and it is an obscure way of doing it.
As always, noone replies to my postings but I keep hammering them in there... ;-)
regards, Göran
Goran,
I'm replying ;-)
For me compatibility matters. So:
goran.hultgren@bluefish.se wrote:
Hi all!
Ok, guys. Let's try to calm down and try to resolve this "storm in a pond" (or whatever you say in english). I really like reading all the arguments from all parties involved so far and I really think everyone who has voiced an opinion have had good points.
Sometimes when I read Andrew I lean over to his side, but then reading Richard (who really follow through the rhetoric with such detailed logical reasoning and research it almost hurts my head :-) I lean over to his side.
For anyone just jumping in here this is what I *think* is the current "score":
- We all agree on that Collection>>removeAll: is currently in need of
*something*.
- I *think* we all agree on that adding a Collection>>removeAll would be
a nice addition, given a nice base implementation to fall back on and some concrete implementations in subclasses.
Not exactly: I'm not against such an addition, but then it should be in a protocol stating that it is *not* compatible with other STs. If the standard says 'undefined' it means that you cannot expect compatibility here (and reality agrees with my point of view).
Note (for Richard A. O. (correct shortcut?)): I don't bother about classes far away the standard (e.g. Morphic stuff), but we are talking about the *core* of Smalltalk/s.
- Some parties (including David Griswold and Andrew Greenberg and others
I presume) want us to simply add a check in the beginning of removeAll: to see if the argument is == self and then signal an error.
- Some parties (including Richard O'Keefe and myself and others I
presume) would like to make this case instead "work". That would simply translate into emptying the collection.
Since this case is 'undefined' by the standard, it should raise an error. Idea: raising an ANSIUndefinedException?
- What the ANSI standard says is not clear. We do know however thanks to
Allen Wirfs-Brock that the committe overlooked the case in question and would probably (if given chance to correct it) opt to call it "undefined".
My assumption.
IMHO these two things together makes it uninteresting to talk about ANSI in this case, and even if it was interesting Squeak is by no means bound by ANSI anyway.
It's *interesting* to talk about ANSI here, if compatibility counts.
- Richard has proposed several variants of making it work including
using #copy. One variant I myself like is to simply call "self removeAll" in this case and let that method deal with it as it please.
- We do know that there are at least two other Smalltalks behaving
differently so there is no given way to operate in order to be "compatible" (In VW it fails, in IBM Smalltalk it works).
There is a way: just avoid using 'undefined' features. Then programs are compatible for both (porting) directions. But to able to do so, you have to *know*, that some feature is 'undefined' and therefore incompatible.
- Richard together with Jan Hylands has done some really interesting
archeological findings indicating that removeAll: indeed did work earlier due to differences in #removeIndex:! And more efficiently too, if I am not mistaken.
See other mail of mine subjected Place for efficiency improvement [was: Re: [BUG]Collection>>removeAll:] .
So the questions are (if anyone has forgotten):
- Should we simply signal error or make "x removeAll: x" work by making
x empty?
Signal error.
- Should we add a Collection>>removeAll with a base implementation and
suitable subclass implementations?
I'm not against this: but please in a protocol stating by its name that it is incompatible with other STs, please.
Greetings,
Stephan
Personal current opinion:
- Andrew had some nice arguments there for a while about "semantic
rules" on not changing operands during operation etc but in the end I got swayed over back to Richard's side, *especially* after the recent archeological findings, but also mainly because the "semantic rules" seemingly to me should only apply when talking about iteration and even though one way of implementing removeAll: is through a simple iteration
- that is not the only way to do it and the iteration is *inside*
removeAll: so it should not be a problem of the caller.
- Adding removeAll is a good thing IMHO. Can't possibly see why having
to write "x removeAll: x copy" would be better - it can't be efficiently reimplemented in subclasses and it is an obscure way of doing it.
As always, noone replies to my postings but I keep hammering them in there... ;-)
regards, Göran
Hi all!
Stephan Rudlof sr@evolgo.de wrote:
Goran,
I'm replying ;-)
Yiha! At last... I was almost starting to believe I was either being ignored because of utter stupidity or my postings weren't getting through... :-)
For me compatibility matters. So:
goran.hultgren@bluefish.se wrote:
Hi all!
Ok, guys. Let's try to calm down and try to resolve this "storm in a pond" (or whatever you say in english). I really like reading all the arguments from all parties involved so far and I really think everyone who has voiced an opinion have had good points.
Sometimes when I read Andrew I lean over to his side, but then reading Richard (who really follow through the rhetoric with such detailed logical reasoning and research it almost hurts my head :-) I lean over to his side.
For anyone just jumping in here this is what I *think* is the current "score":
- We all agree on that Collection>>removeAll: is currently in need of
*something*.
- I *think* we all agree on that adding a Collection>>removeAll would be
a nice addition, given a nice base implementation to fall back on and some concrete implementations in subclasses.
Not exactly: I'm not against such an addition, but then it should be in a protocol stating that it is *not* compatible with other STs. If the standard says 'undefined' it means that you cannot expect compatibility here (and reality agrees with my point of view).
Good suggestion.
Note (for Richard A. O. (correct shortcut?)): I don't bother about classes far away the standard (e.g. Morphic stuff), but we are talking about the *core* of Smalltalk/s.
Sure, I agree. If we can then we should take care of showing where we have added stuff beyond ANSI and other Smalltalks etc.
- Some parties (including David Griswold and Andrew Greenberg and others
I presume) want us to simply add a check in the beginning of removeAll: to see if the argument is == self and then signal an error.
- Some parties (including Richard O'Keefe and myself and others I
presume) would like to make this case instead "work". That would simply translate into emptying the collection.
Since this case is 'undefined' by the standard, it should raise an error. Idea: raising an ANSIUndefinedException?
I assume your reasons are that you do not want people to write Squeak code relying on such code working and then get slapped on their hands when porting to other Smalltalks? That is probably the first reason I really like. :-)
As long as we add removeAll then this is fine by me, otherwise it is not.
- What the ANSI standard says is not clear. We do know however thanks to
Allen Wirfs-Brock that the committe overlooked the case in question and would probably (if given chance to correct it) opt to call it "undefined".
My assumption.
I agree in principal with Richard saying that a standard should be read "to the letter" and not based on anything else. But since obviously different people interpret the standard differently we should *simply because of that fact* choose the conservative error route. I think. :-) I change my mind so often my head spins...
IMHO these two things together makes it uninteresting to talk about ANSI in this case, and even if it was interesting Squeak is by no means bound by ANSI anyway.
It's *interesting* to talk about ANSI here, if compatibility counts.
Well, of course. I was thinking more in lines of the fact that ANSI is unclear. But given the porting argument above I can see the point.
- Richard has proposed several variants of making it work including
using #copy. One variant I myself like is to simply call "self removeAll" in this case and let that method deal with it as it please.
- We do know that there are at least two other Smalltalks behaving
differently so there is no given way to operate in order to be "compatible" (In VW it fails, in IBM Smalltalk it works).
There is a way: just avoid using 'undefined' features. Then programs are compatible for both (porting) directions. But to able to do so, you have to *know*, that some feature is 'undefined' and therefore incompatible.
- Richard together with Jan Hylands has done some really interesting
archeological findings indicating that removeAll: indeed did work earlier due to differences in #removeIndex:! And more efficiently too, if I am not mistaken.
See other mail of mine subjected Place for efficiency improvement [was: Re: [BUG]Collection>>removeAll:] .
So the questions are (if anyone has forgotten):
- Should we simply signal error or make "x removeAll: x" work by making
x empty?
Signal error.
- Should we add a Collection>>removeAll with a base implementation and
suitable subclass implementations?
I'm not against this: but please in a protocol stating by its name that it is incompatible with other STs, please.
Sure! :-)
Greetings,
Stephan
regards, Göran "changing his mind once again" Hultgren
goran.hultgren@bluefish.se wrote:
For anyone just jumping in here this is what I *think* is the current "score": [...]
- Richard together with Jan Hylands has done some really interesting
archeological findings indicating that removeAll: indeed did work earlier due to differences in #removeIndex:! And more efficiently too, if I am not mistaken.
No, Richard noticed some recent changes and speculated that it might have worked previously. Jon found that Squeak's #removeIndex: in fact worked in the same inefficient way it does now (only without even the benefit of a primitive call) as far back as 1.1. It's still the case that Richard's suggestion could probably make this operation more efficient in many common cases.
Otherwise, I think your summary is accurate.
[...]
Personal current opinion:
- Andrew had some nice arguments there for a while about "semantic
rules" on not changing operands during operation etc but in the end I got swayed over back to Richard's side, *especially* after the recent archeological findings, but also mainly because the "semantic rules" seemingly to me should only apply when talking about iteration and even though one way of implementing removeAll: is through a simple iteration
- that is not the only way to do it and the iteration is *inside*
removeAll: so it should not be a problem of the caller.
I agree, except that I wouldn't place much weight on the archeological evidence either way. It's interesting, but I don't think it informs us much on what we *should* do. One of the goals of Squeak is to evolve a better system; that can't happen if we bind ourselves to past behavior. The question we should really focus on is, which solution is better? The answer to that depends on one's personal priorities between simplicity of implementation, simplicity (and generality) of use, portability between dialects, and efficiency. Then there are one's views on semantic consistency.
The difference between the implementation simplicity of most of Richard's suggested non-error-throwing implementations and the various error-throwing versions suggested is minimal. Richard has also admirably demonstrated that it can be done with no measurable loss to efficiency, or even with a significant performance boost compared to the status quo. (In all fairness, I should note that the same techniques could be applied to a non-copying implementation to gain an even greater speed boost. Still, if the performance of these operations in the status quo is remotely acceptable, so would be Richard's copying version.)
Personally, I think it's clear that simplicity and generality of use is improved by expanding coverage to include the receiver as argument. There are fewer special cases to worry about. I think Andrew has quite convincingly argued that checking this special case is not sufficient to ensure correctness in all cases if there are, for example, slicing collections. I vacillate on the importance of the fact that there are none in the base image. However, I think a copying implemantation (or any other that avoids removing the elements from the receiver while iterating over the argument) would be fully general and applicable to all cases without exception. From a usability standpoint, how is this anything but ideal?
As for semantic rules and consistency, in particular about not modifying a collection over which one is iterating, I have to agree with Richard, Goran, and the others who take the view that #removeAll: should not be externally viewed as an iteration operation. Richard has again done much legwork to support his point here, by questioning both technically experienced and inexperienced parties about their expectations. It seems that people, by and large, do not expect #removeAll: to behave externally as an iteration construct. Furthermore, it is not classified by Squeak as an iteration construct: it is classified under "removing" protocol, not "enumerating".
(Aside: If we go back to a usability/generality point of view, I think copying semantics for #do: would in fact be a big improvement. Unfortunately, in this case the performance hit is just too much.)
Portability is also a relevant concern. I do think there's merit to writing prtable code, and to helping an author detect deviations from the standard. Throwing an error for undefined results can be a fine way to do this. However, I don't think this should override the usability of the system when a sensible result can be provided for those who wish to use it, and accept the consequent limitations to the portability of their code. It's like intentionally returning a special UndefinedResult object in all those cases where the return value is undefined. Rather than cramming the limitations of the standard (or of every other dialect, where the standard is unclear or silent), it would be better to have tools to assist, or a behavior switch, or a strict portability-enforcing module, that could be turned on when desired, or ignored by those who don't want it.
- Adding removeAll is a good thing IMHO. Can't possibly see why having
to write "x removeAll: x copy" would be better - it can't be efficiently reimplemented in subclasses and it is an obscure way of doing it.
I think this is an obviously good addition. Anyone maintaining or using extant parallel implementations of the Collection protocol can easily add it.
-Jesse
"Andrew C. Greenberg" werdna@mucow.com is claimed by the authorities to have written:
Here is the crux of the argument. In my view, the iteration is implied and, moreover, is impossible to define meaningfully without using some language of iteration, directly or indirectly.
I _might_ possibly agree with this part, given a charitable frame of mind and a trailing throttle.
Whether the iterand is a receiver, parameter or the result of an expression does not, to me, seem relevant. So, too, it seems that many Smalltalk illuminati agree.
I believe I can claim to reasonably illuminated after all these years and I really have to demurr. There are so many possible ways that code involved in an iteration can end up messing with the order of the elements in a collection that it's not even funny. The chief problem I have with the argument that one should know when some code is going to cause this problem is that it utterly smashes the nice story we so often try to sell about encapsulation et al. If I have to know somehow that my code is plausibly going to trigger a problem I may very well end up having to delve into areas of the system that I don't want to worry about, don't understand, or conceivably can't get the code for. This does not strike me as a good scenario.
The specific instance of removeAll: and it's close relatives is not terrifically interesting. I'm sure we could solve it by some means involving sticking appropriate nulls in the removed places and cleaning up at the end of the operation, in a manner analogous to the tricks used to save redisplayingthe Display on every little draw. Or something equally trivial.
The general case strikes me as much more of a problem and sadly much harder to imagine a solution for. My best guess so far is to raise a signal when a method changes the structure and to require handling it to fix up problems. Unfortunately it is current absurdly runtime expensive to set up an exception handler situation and almost nobody ever bothers to use exceptions, so I doubt this would work in practice. Conceptually it feels like it has some merit, so perhaps some further though might be worthwhile.
tim
The specific instance of removeAll: and it's close relatives is not terrifically interesting. I'm sure we could solve it by some means involving sticking appropriate nulls in the removed places and cleaning up at the end of the operation, in a manner analogous to the tricks used to save redisplayingthe Display on every little draw. Or something equally trivial.
Trivial is good. Attached is my little sliver of understanding about the problem. One other little quandry I came by in trying this is
foo := #(1 2 3) asOrderedCollection. foo add: foo. foo asBag. "Boom. Stack all over the place, children crying, etc."
'From Squeak3.2gamma of 15 January 2002 [latest update: #4881] on 1 September 2002 at 1:15:12 am'!
!OrderedCollection methodsFor: 'adding' stamp: 'bmk 9/1/2002 00:57'! truncate:aSize aSize > self size ifTrue:[ self error: 'ENOTPOSIX'] ifFalse:[lastIndex := firstIndex+aSize-1]! !
!OrderedCollection methodsFor: 'removing' stamp: 'bmk 9/1/2002 01:12'! removeAll: aCollection |removalSet sentinel|
removalSet := aCollection asBag. sentinel := Object new. 1 to: self size do:[:index| (removalSet includes:(self at:index)) ifTrue:[ self at:index put:sentinel ]. ]. self squeegee:sentinel. ^aCollection. "don't know why. Nobody wants it"! !
!OrderedCollection methodsFor: 'removing' stamp: 'bmk 9/1/2002 01:00'! squeegee: sentinel "remove all instances of the sentinel, and squish down the array" |tightIndex| tightIndex := 1. 1 to: self size do: [:fluffedIndex| "probably safer to manually iterate here than trust #do:" (self at: fluffedIndex) == sentinel ifFalse:[ self at:tightIndex put: (self at:fluffedIndex). tightIndex := tightIndex+1. ]. ]. self truncate:tightIndex -1 ! !
squeak-dev@lists.squeakfoundation.org