Scripsit ille ?Brian Candler? <B.Candler / pobox.com>:
> 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()