>> (...)
I think modifying a collection while iterating over it is undefined.
Ah, doggone it, that was my third choice between "is it a bug or is it
not". And I kept saying to myself as I wrote that up "Remember, you
should never claim something is a bug unless you're really really
sure."
I'll accept this answer as reasonable, though it seems a bit of a
shame. Always relegating deletions to a delete_if or reject or
explicit calls while iterating a duplicate may be inefficient in some
cases where complex logic needs to happen and ideally happen in a
single pass through the data.
I think you left out plenty of options here. Of course, it depends on
the issue at hand but you can do at least these
1. Iterate through keys only
hash.keys.each {|k| ... }
This is safe for inserts and deletions because Hash#keys creates a
new, unrelated Array. This fits well your original example since you
are using keys only anway.
2. Using delete_if directly
hash.delete_if {|k,v| ...}
This method iterates all entries as well as safely deleting particular items.
3. storing keys prepared for deletion separately
del =
hash.each {|k,v| .... del << k if ...}
del.each {|k| hash.delete k}
It's 'safest' to say "DON'T TRUST ANYTHING, EVER", but provides the
lest flexibility during coding. It's most dangerous to say "YOU CAN
EXPLOIT WHATEVER IMPLEMENTATION QUIRKS AND BUGS THE CURRENT VERSION
HAS, WE PROMISE TO KEEP THOSE IN FUTURE VERSIONS", but also may
provide for convenient or efficiency via 'tricky' coding. Somewhere in
between is (my) ideal.
If changing during iteration is yields undefined results it is
perfectly ok to in one case endlessly loop and in the other do
something else. Code which does something forbidden is never safe.
Imagine if SQL said "Inserting multiple records use a select on the
table you are inserting into is undefined." Programmers everywhere
would nod their heads wisely and say "Good call". And some
implementations would allow it, some wouldn't, and some people would
accidentally rely on it, and others would be pissed to not be able to.
IMHO SQL is a bad language for an example because it has a dramatic
different nature than Ruby: SQL is declarative while Ruby is
procedural. Apart from that there is a SQL standard which defines
legal and illegal constructs.
Before it seems like I'm taking a strong stand for modification during
iteration: In my opinion, the only problem in this situation is simply
that the documentation for Hash#each provides no information. "We've
gotta be able to get some kind of reading on that shield, up or down!"
Ideally, in my mind, we should document:
a) [Implementation] How each iterating method happens to behave
currently with respect to additions, modifications, and deletions
during traversal, and
b) [Design] What is intended to be true about the implementation for
(foreseeable) future versions, and may be relied upon.
I agree, this should be documented. OTOH it is very common for
programming languages to not allow modifications of at least some
types of collections during iteration. So I personally do not expect
it to work unless explicitly stated - especially for hash based
structures which can change dramatically with the insertion of a
single entry just because of the way hash tables work.
I'll take this to ruby-core and see if I can gather details on Design
for a variety of methods, and offer up a doc patch. If anyone here can
provide any details about either Implementation or (for sure) Design,
I'd be happy to hear it.
AFAIK modification during iteration with #each is never safe for
Array, Hash (and thus also Set).
Kind regards
robert
···
2009/2/16 Phrogz <phrogz@mac.com>:
On Feb 15, 8:35 am, Pit Capitain <pit.capit...@gmail.com> wrote:
2009/2/15 David A. Black <dbl...@rubypal.com>:
> On Sun, 15 Feb 2009, Phrogz wrote:
--
remember.guy do |as, often| as.you_can - without end