Next, retry, break?


#1

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


#2

removed_email_address@domain.invalid 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


#3

Quoting removed_email_address@domain.invalid:

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

Sorry, s/retry/redo/.

-mental


#4

On 12/9/05, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

Quoting removed_email_address@domain.invalid:

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 F.


#5

Quoting Jacob F. removed_email_address@domain.invalid:

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


#6

Hi,

In message “Re: next, retry, break?”
on Sat, 10 Dec 2005 05:59:29 +0900, removed_email_address@domain.invalid 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.

#7

On Sat, 2005-12-10 at 11:29 +0900, Yukihiro M. 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


#8

Hi,

In message “Re: next, retry, break?”
on Sat, 10 Dec 2005 14:01:43 +0900, MenTaLguY removed_email_address@domain.invalid
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.

#9

On Sat, 2005-12-10 at 14:34 +0900, Yukihiro M. 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


#10

On Sun, 2005-12-11 at 09:03 +0900, Yukihiro M. 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


#11

Hi,

In message “Re: next, retry, break?”
on Sun, 11 Dec 2005 04:40:33 +0900, MenTaLguY removed_email_address@domain.invalid
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.