From: Rudolf Polzer Date: 2003-08-08T01:46:20+09:00 Subject: Re: Elegant solution for a loop-break problem Scripsit ille �Brian Candler� : > On Fri, Aug 08, 2003 at 12:00:15AM +0900, KONTRA Gergely wrote: > > > > How can I alter items in a hash nicely? > > > > > > h[key] = value # replace it > > > h[key] << value # append to existing array > > > > Oops. Sorry. The question was dumb. I mean: I want to iterate over a > > hash and change some elements. (in perl you get the elements, not copys > > of the elements. > > In Ruby you get the elements, not copies of the elements. > > If you want to replace elements with completely new objects, you could do > > h.each_key do |k| > h[k] = new_element > end > > But otherwise you call whatever mutator method you like on the object which > is in the hash: > > h.each do |e| > e.change_my_state > end he means, that in Perl you can do: use Data::Dumper; @l = qw(one two three); for my $elem(@l) { $elem = 'X' . uc $elem; } print Dumper \@l; which yields $VAR1 = [ 'XONE', 'XTWO', 'XTHREE' ]; while something which looks like its equivalent in Ruby: l = %w{one two three} l.each() do |elem| elem = 'X' + elem.upcase() end p l does not work like a Perl programmer expects: ["one", "two", "three"] Of course, elem.upcase!() would solve the problem... but in this case, eventually the equivalent of Perl's map operator, Array#collect and Array#collect!, are more appropiate: l = %w{one two three} l.collect!() do |elem| 'X' + elem.upcase() end p l which works. BTW, one can use "next" instead of return to yield a return value: l = %w{one two three} l.collect!() do |elem| next elem if elem.match(/wo/) next 'four' if elem == 'three' 'X' + elem.upcase() end p l But, as stated in the other posting, that's a 1.8 feature. Contrary to next, break works the usual way. def tester(&block) 5.times() do |x| p :before => x y = yield x p :after => y end p :end 42 end p (tester do |x| next 'CAUGHT' if x == 1 break 'BROKEN' if x == 3 x end) So the next doesn't exit a loop iteration (the second :after wouldn't have been printed then) but returns a value from a block. Looks like an intended feature. The break, however, exits the block-using method 'tester'. Somehow looks suspicious to me. But it's a yield feature and can be circumvented by calling he block directly; the break can be "caught": 3.times() do |t| p t begin break 17 ensure next end end prints 0, 1 and 2. The break value however is lost. I think it would be a good idiom if one puts "ensure next" in the same line... since this construct is to ensure that there'll be every loop iteration and breaks will be ignored (transformed into nexts, that is). But, somehow I don't like it: 3.times() do |t| p t begin raise "StupidError" ensure next end end Even the exception is silently ignored. 3.times() do |t| p t begin throw :ball ensure next end end "works" the same way. Somehow "ensure" seems to be too strong for me... can one check WHY one is in an ensure block, that is, because of a return, a break, a next, an exception or in the normal program flow? As this program shows, there seems to be only one way to get out; to make it less obvious, I used random order of breaking-out attempts: def unbreakable(&block) loop do begin yield ensure next end end end def thefooledone() cont = callcc() do |c| c end if cont catch :ball do unbreakable do i = rand(6) p :this_time => i case i when 0 then return 42 when 1 then break when 2 then cont.call() when 3 then next when 4 then raise "ID10T" when 5 then throw :ball end end end end end thefooledone()