Forum: Ruby next, retry, break?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-09 22:05
(Received via mailing list)
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
32edd0717b3144d5c58a352d613abdc9?d=identicon&s=25 surrender_it (Guest)
on 2005-12-09 22:34
(Received via mailing list)
mental@rydia.net ha scritto:
>      @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
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-09 22:50
(Received via mailing list)
Quoting mental@rydia.net:

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

Sorry, s/retry/redo/.

-mental
Cff9eed5d8099e4c2d34eae663aae87e?d=identicon&s=25 lukfugl (Guest)
on 2005-12-09 23:14
(Received via mailing list)
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/.

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
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-10 00:12
(Received via mailing list)
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
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 matz (Guest)
on 2005-12-10 03:32
(Received via mailing list)
Hi,

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

|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.
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-10 06:03
(Received via mailing list)
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.

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
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 matz (Guest)
on 2005-12-10 06:35
(Received via mailing list)
Hi,

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

|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.
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-10 20:41
(Received via mailing list)
On Sat, 2005-12-10 at 14:34 +0900, Yukihiro Matsumoto wrote:
> |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
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 matz (Guest)
on 2005-12-11 01:06
(Received via mailing list)
Hi,

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

|> 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.
912c61d9da47754de7039f4271334a9f?d=identicon&s=25 mental (Guest)
on 2005-12-11 02:52
(Received via mailing list)
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.

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
This topic is locked and can not be replied to.