Next, retry, break?

Okay, I've got a question.

What's the correct way to implement a custom Enumerable#each which
handles next, retry and break correctly?

For example:

def each
   while @current_grob
     yield @current_grob.create
     @current_grob = @current_grob.next_grob
   end
end

How do I make next, retry, and break work as expected?

-mental

mental@rydia.net ha scritto:

Okay, I've got a question.

What's the correct way to implement a custom Enumerable#each which
handles next, retry and break correctly?

For example:

def each
   while @current_grob
     yield @current_grob.create
     @current_grob = @current_grob.next_grob
   end
end

How do I make next, retry, and break work as expected?

-mental

don't they do it already?
>> Triple = Struct.new :a, :b, :c do
?> def each
>> yield a
>> yield b
>> yield c
>> end
>> end
=> Triple
>> Triple.new(1,2,3).each do |x|
?> if x == 1
>> next
>> elsif x == 2
>> p "2"
>> break
>> end
>> p x
>> end
"2"
=> nil

Quoting mental@rydia.net:

How do I make next, retry, and break work as expected?

Sorry, s/retry/redo/.

-mental

Hi,

Okay, I've got a question.

What's the correct way to implement a custom Enumerable#each which
handles next, retry and break correctly?

Just do it normally. You don't have to do nothing for them.

For example:

def each
  while @current_grob
    yield @current_grob.create
    @current_grob = @current_grob.next_grob
  end
end

How do I make next, retry, and break work as expected?

If you want "redo" to work, you have to initialize your @current_grob
at the beginning of the iterating method. Since many expect "each" to
enumerate all the values in the collection, initializing at the top is
a good thing in general.

              matz.

···

In message "Re: next, retry, break?" on Sat, 10 Dec 2005 05:59:29 +0900, mental@rydia.net writes:

Like Gabriele said in the other thread, they seem to already...

$ cat test.rb
lukfugl@falcon:~$ cat test.rb
class MyArray
  include Enumerable

  def initialize( *ary )
    @ary = ary
  end

  def each
    @ary.each { |el| yield el }
  end
end

def test( ary, *sequence )
  puts "### #{ary.class}, #{sequence.inspect} ###"
  ary.each do |el|
    puts el
    case sequence.shift
    when :next then next
    when :retry then retry
    when :redo then redo
    when :break then break
    end
  end
end

ary1 = [ 1, 2, 3, 4, 5 ]
ary2 = MyArray.new( *ary1 )

test( ary1, :next, :retry, :next, :next, :redo, :redo, :break )
test( ary2, :next, :retry, :next, :next, :redo, :redo, :break )

$ ruby test.rb
### Array, [:next, :retry, :next, :next, :redo, :redo, :break] ###
1
2
1
2
3
3
3
### MyArray, [:next, :retry, :next, :next, :redo, :redo, :break] ###
1
2
1
2
3
3
3

Jacob Fugal

···

On 12/9/05, mental@rydia.net <mental@rydia.net> wrote:

Quoting mental@rydia.net:

> How do I make next, retry, and break work as expected?

Sorry, s/retry/redo/.

Thanks...

This is for a stream class, so it would be a "consuming" each like
IO#each. In that case not initializing is okay (it should behave just
like IO#each does). The problem was that I was not advancing the stream
pointer until after the yield -- solved with 'ensure'.

Just to be sure I understand correctly now:

redo: jumps to the start of the block

next: jumps to the end of the block
       using the given value (or nil)
       as the block's result

retry: unwinds the stack to the call
        site of the "closest" yielding
        method, calling it again with
        the same arguments

break: unwinds the stack to the call
        site of the "closest" yielding
        method, using the given value
        (or nil) as the method's result

Is that right?

If so, what if I want retry or break to unwind further (e.g. because I
am implementing one iterator in terms of another)?

-mental

···

On Sat, 2005-12-10 at 11:29 +0900, Yukihiro Matsumoto wrote:

If you want "redo" to work, you have to initialize your @current_grob
at the beginning of the iterating method. Since many expect "each" to
enumerate all the values in the collection, initializing at the top is
a good thing in general.

Quoting Jacob Fugal <lukfugl@gmail.com>:

Like Gabriele said in the other thread, they seem to already...

Well, yes and no. You didn't try anything like my example (which
does fail), but on the other hand it seems I did subtly
misunderstand the behavior of these keywords (particularly retry).

The example I gave can be fixed by adding a begin...ensure, more or
less as I had been advising others (though I wasn't always
recommending it in the right places). With the ensure, it still
behaves differently than Array#each, but consistently with other
iterators which have persistent state (e.g. IO#each).

Along those lines, my comment in the earlier thread about IO#each
not bothering to implement retry was also incorrect; its behavior
makes sense once you understand what retry really does.

Thank you for putting together the test harness, that helped me get
myself straight.

-mental

Hi,

Just to be sure I understand correctly now:

redo: jumps to the start of the block

next: jumps to the end of the block
      using the given value (or nil)
      as the block's result

retry: unwinds the stack to the call
       site of the "closest" yielding
       method, calling it again with
       the same arguments

break: unwinds the stack to the call
       site of the "closest" yielding
       method, using the given value
       (or nil) as the method's result

Is that right?

Right, if the term "closest" means what I thought.

If so, what if I want retry or break to unwind further (e.g. because I
am implementing one iterator in terms of another)?

I'm not sure what you want. "retry" and "break" jumps out of the
method immediately, so that you can do nothing. But you can use
"ensure" if you really need iteration termination process.

              matz.

···

In message "Re: next, retry, break?" on Sat, 10 Dec 2005 14:01:43 +0900, MenTaLguY <mental@rydia.net> writes:

> retry: unwinds the stack to the call
> site of the "closest" yielding
> method, calling it again with
> the same arguments
>
> break: unwinds the stack to the call
> site of the "closest" yielding
> method, using the given value
> (or nil) as the method's result
>
>Is that right?

Right, if the term "closest" means what I thought.

I think the word I wanted was "innermost"..?

>If so, what if I want retry or break to unwind further (e.g. because I
>am implementing one iterator in terms of another)?

I'm not sure what you want. "retry" and "break" jumps out of the
method immediately, so that you can do nothing. But you can use
"ensure" if you really need iteration termination process.

I just tried this and it works fine:

class Spleen
   def initialize
     @arr = [1, 2, 3]
   end
   def each(&blk)
     @arr.each(&blk)
     self
   end
end

s = Spleen.new
k = s.each { break 42 }
p k #=> 42

I'm happy now, except (given my understanding of 'break' behavior above)
I don't understand why it works. Does the call to Array#each with &blk
count as the innermost yield? Or does it cause the yield in Array#each
to be accounted to Spleen#each?

Otherwise, it seems like it is unwinding to the method call to which the
block is originally attached, rather than only to the innermost yielding
one.

Thank you for your patience,

-mental

···

On Sat, 2005-12-10 at 14:34 +0900, Yukihiro Matsumoto wrote:

Hi,

Right, if the term "closest" means what I thought.

I think the word I wanted was "innermost"..?

Then, I think you were wrong.

I just tried this and it works fine:

class Spleen
  def initialize
    @arr = [1, 2, 3]
  end
  def each(&blk)
    @arr.each(&blk)
    self
  end
end

s = Spleen.new
k = s.each { break 42 }
p k #=> 42

I'm happy now, except (given my understanding of 'break' behavior above)
I don't understand why it works. Does the call to Array#each with &blk
count as the innermost yield? Or does it cause the yield in Array#each
to be accounted to Spleen#each?

"break" breaks the lexically closest one, in this case Spleen.each,
not Array#each called within Spleen#s.

              matz.

···

In message "Re: next, retry, break?" on Sun, 11 Dec 2005 04:40:33 +0900, MenTaLguY <mental@rydia.net> writes:

Thank you! That was the root of my confusion. I was under the mistaken
impression that the "target" of break/retry was determined dynamically
rather than lexically.

Thanks again for your patience,

-mental

···

On Sun, 2005-12-11 at 09:03 +0900, Yukihiro Matsumoto wrote:

"break" breaks the lexically closest one, in this case Spleen.each,
not Array#each called within Spleen#s.