# Lazy evaluation?

**URL:** <https://rubytalk.org/t/lazy-evaluation/24691>\
**Category:** ruby-talk\
**Created:** [7 February 2006 09:58 UTC](https://rubytalk.org/t/lazy-evaluation/24691 "2006-02-07T09:58:20Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Martin\_DeMello1](https://avatars.discourse-cdn.com/v4/letter/m/3be4f8/32.png) [@Martin\_DeMello1](https://rubytalk.org/u/Martin_DeMello1)\
**Post date:** [7 February 2006 09:58 UTC](https://rubytalk.org/t/lazy-evaluation/24691/1 "2006-02-07T09:58:20Z")

</div>

Could someone explain why this code works:

&nbsp;&nbsp;&nbsp;&nbsp;def repeat(condition)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "condition: #{condition}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;retry if not condition  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;j=0  
&nbsp;&nbsp;&nbsp;&nbsp;repeat (j \>= 10) do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts j  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;j+=1  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;puts "after loop, j = #{j}"

I'd have expected repeat (j \>= 10) to pass "false" into the method,  
which would then yield repeatedly to the do/end block, never seeing the  
(condition) part again.

martin

---

<div class="post-metadata">

**Author:** ![Robert](https://avatars.discourse-cdn.com/v4/letter/r/e95f7d/32.png) [@Robert](https://rubytalk.org/u/Robert)\
**Post date:** [7 February 2006 11:23 UTC](https://rubytalk.org/t/lazy-evaluation/24691/2 "2006-02-07T11:23:22Z")

</div>

Martin DeMello wrote:

> Could someone explain why this code works:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def repeat(condition)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "condition: #{condition}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;retry if not condition  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;j=0  
> &nbsp;&nbsp;&nbsp;&nbsp;repeat (j \>= 10) do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts j  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;j+=1  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;puts "after loop, j = #{j}"
> 
> I'd have expected repeat (j \>= 10) to pass "false" into the method,  
> which would then yield repeatedly to the do/end block, never seeing  
> the (condition) part again.

retry actually reevaluates the method invocation. If you use  
set\_trace\_func you'll see something like this with the attached script:

["line", "/c/temp/ruby/retry.rb", 8, nil, #\<Binding:0x100f63e8\>, false]  
["line", "/c/temp/ruby/retry.rb", 9, nil, #\<Binding:0x100f63a0\>, false]  
["c-call", "/c/temp/ruby/retry.rb", 9, :\>=, #\<Binding:0x100f6250\>, Fixnum]  
["c-return", "/c/temp/ruby/retry.rb", 9, :\>=, #\<Binding:0x100f6130\>,  
Fixnum]  
["call", "/c/temp/ruby/retry.rb", 2, :repeat, #\<Binding:0x100f5f08\>,  
Object]  
["line", "/c/temp/ruby/retry.rb", 3, :repeat, #\<Binding:0x100f5ec0\>,  
Object]  
["c-call", "/c/temp/ruby/retry.rb", 3, :to\_s, nil, FalseClass]  
["c-return", "/c/temp/ruby/retry.rb", 3, :to\_s, nil, FalseClass]  
["c-call", "/c/temp/ruby/retry.rb", 3, :puts, #\<Binding:0x100f5b30\>,  
Kernel]  
["c-call", "/c/temp/ruby/retry.rb", 3, :write, #\<Binding:0x100f5a10\>, IO]  
condition: false["c-return", "/c/temp/ruby/retry.rb", 3, :write,  
#\<Binding:0x100f58d8\>, IO]  
["c-call", "/c/temp/ruby/retry.rb", 3, :write, #\<Binding:0x100f57a0\>, IO]

["c-return", "/c/temp/ruby/retry.rb", 3, :write, #\<Binding:0x100f5680\>,  
IO]  
["c-return", "/c/temp/ruby/retry.rb", 3, :puts, #\<Binding:0x100f5560\>,  
Kernel]  
["line", "/c/temp/ruby/retry.rb", 4, :repeat, #\<Binding:0x100f5440\>,  
Object]  
["line", "/c/temp/ruby/retry.rb", 10, nil, #\<Binding:0x100f5230\>, false]  
["c-call", "/c/temp/ruby/retry.rb", 10, :puts, #\<Binding:0x100f51e8\>,  
Kernel]  
["c-call", "/c/temp/ruby/retry.rb", 10, :to\_s, #\<Binding:0x100f50c8\>,  
Fixnum]  
["c-return", "/c/temp/ruby/retry.rb", 10, :to\_s, #\<Binding:0x100f4eb8\>,  
Fixnum]  
["c-call", "/c/temp/ruby/retry.rb", 10, :write, #\<Binding:0x100f4e70\>, IO]  
0["c-return", "/c/temp/ruby/retry.rb", 10, :write, #\<Binding:0x100f4d50\>,  
IO]  
["c-call", "/c/temp/ruby/retry.rb", 10, :write, #\<Binding:0x100f4c30\>, IO]

["c-return", "/c/temp/ruby/retry.rb", 10, :write, #\<Binding:0x100f4b10\>,  
IO]  
["c-return", "/c/temp/ruby/retry.rb", 10, :puts, #\<Binding:0x100f49f0\>,  
Kernel]  
["line", "/c/temp/ruby/retry.rb", 11, nil, #\<Binding:0x100f48d0\>, false]  
["c-call", "/c/temp/ruby/retry.rb", 11, :+, #\<Binding:0x100f47b0\>, Fixnum]  
["c-return", "/c/temp/ruby/retry.rb", 11, :+, #\<Binding:0x100f4690\>,  
Fixnum]  
["line", "/c/temp/ruby/retry.rb", 5, :repeat, #\<Binding:0x100f4570\>,  
Object]  
["line", "/c/temp/ruby/retry.rb", 5, :repeat, #\<Binding:0x100f4450\>,  
Object]  
["return", "/c/temp/ruby/retry.rb", 3, :repeat, #\<Binding:0x100f4330\>,  
Object]  
["c-call", "/c/temp/ruby/retry.rb", 9, :\>=, #\<Binding:0x100f4210\>, Fixnum]  
["c-return", "/c/temp/ruby/retry.rb", 9, :\>=, #\<Binding:0x100f40f0\>,  
Fixnum]  
["call", "/c/temp/ruby/retry.rb", 2, :repeat, #\<Binding:0x100f3ee0\>,  
Object]  
["line", "/c/temp/ruby/retry.rb", 3, :repeat, #\<Binding:0x100f3e98\>,  
Object]  
["c-call", "/c/temp/ruby/retry.rb", 3, :to\_s, nil, FalseClass]  
["c-return", "/c/temp/ruby/retry.rb", 3, :to\_s, nil, FalseClass]  
["c-call", "/c/temp/ruby/retry.rb", 3, :puts, #\<Binding:0x100f3b38\>,  
Kernel]  
["c-call", "/c/temp/ruby/retry.rb", 3, :write, #\<Binding:0x100f3a18\>, IO]  
condition: false["c-return", "/c/temp/ruby/retry.rb", 3, :write,  
#\<Binding:0x100f38f8\>, IO]  
....

You see that it goes back to line 9 reevaluating the \>=. If you ask why  
this is (i.e. design rationale), you probably have to ask Matz. 🙂 HTH

Kind regards

&nbsp;&nbsp;&nbsp;&nbsp;robert

[retry.rb](https://rubytalk.org/uploads/short-url/yLp3ebX9WXOxAesF8OCVID3UFPO.rb) (186 Bytes)

---

<div class="post-metadata">

**Author:** ![Vidar\_Hokstad](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vidar\_Hokstad](https://rubytalk.org/u/Vidar_Hokstad)\
**Post date:** [7 February 2006 11:38 UTC](https://rubytalk.org/t/lazy-evaluation/24691/3 "2006-02-07T11:38:21Z")

</div>

Martin DeMello wrote:

> Could someone explain why this code works:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def repeat(condition)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "condition: #{condition}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;retry if not condition  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;j=0  
> &nbsp;&nbsp;&nbsp;&nbsp;repeat (j \>= 10) do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts j  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;j+=1  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;puts "after loop, j = #{j}"
> 
> I'd have expected repeat (j \>= 10) to pass "false" into the method,  
> which would then yield repeatedly to the do/end block, never seeing the  
> (condition) part again.

It's your assumption of where the "retry" restarts execution that's  
wrong... Essentially the retry in "repeat" will restart execution right  
before the call to repeat, not at the start of the "repeat" method,  
thus evaluating the arguments again.

You can see that by changing your code so that there's an observable  
side effect of the reevaluation:

> **···**
>
> ####
> 
> def cond(c)  
> &nbsp;&nbsp;puts "Reevaluating the condition"  
> &nbsp;&nbsp;c  
> end
> 
> def repeat(condition)  
> &nbsp;&nbsp;puts "condition: #{condition}"  
> &nbsp;&nbsp;yield  
> &nbsp;&nbsp;retry if not condition  
> end
> 
> j=0  
> repeat (cond(j \>= 10)) do  
> &nbsp;&nbsp;puts j  
> &nbsp;&nbsp;j+=1  
> end
> 
> puts "after loop, j = #{j}"
> 
> ####
> 
> It surprised me too, but it is much more useful that way.
> 
> Vidar

---

<div class="post-metadata">

**Author:** ![Joel\_VanderWerf1](https://avatars.discourse-cdn.com/v4/letter/j/94ad74/32.png) [@Joel\_VanderWerf1](https://rubytalk.org/u/Joel_VanderWerf1)\
**Post date:** [7 February 2006 15:16 UTC](https://rubytalk.org/t/lazy-evaluation/24691/4 "2006-02-07T15:16:20Z")

</div>

Martin DeMello wrote:

> Could someone explain why this code works:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def repeat(condition)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "condition: #{condition}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;retry if not condition  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;j=0  
> &nbsp;&nbsp;&nbsp;&nbsp;repeat (j \>= 10) do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts j  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;j+=1  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;puts "after loop, j = #{j}"
> 
> I'd have expected repeat (j \>= 10) to pass "false" into the method,  
> which would then yield repeatedly to the do/end block, never seeing the  
> (condition) part again.
> 
> martin

What confused me about this is that retry has two meanings, one in the  
context of a loop or block, and another in the context of begin...end  
(inside a rescue clause). From PickAxe v2:

> break terminates the immediately enclosing loop--control resumes at  
> the statement following the block. redo repeats the loop from the  
> start, but without reevaluating the condition or fetching the next  
> element (in an iterator). The next keyword skips to the end of the  
> loop, effectively starting the next iteration. retry restarts the  
> loop, reevaluating the condition. [p.330]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Apparently, this includes the arguments of the method, in  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;the case of an iterator method called with a block.

> The retry statement can be used within a rescue clause to restart the  
> enclosing begin/end block from the beginning. [p. 347]

Which interpretation is used can depend on whether a block is present:

$ cat thrice.rb  
def thrice(x)  
&nbsp;&nbsp;@count = 0 if !@count || @count \>= 3  
&nbsp;&nbsp;@count += 1  
&nbsp;&nbsp;retry if @count \< 3  
end

thrice puts("foo") do end  
thrice puts("foo")

$ ruby thrice.rb  
foo  
foo  
foo  
foo  
thrice.rb:4:in `thrice': retry outside of rescue clause (LocalJumpError)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from thrice.rb:8

So there's no need to worry about object.foo(ary.pop) reevaluating its  
argument. That can only happen if a block is present, in which case the  
caller should be aware of any "control structure" semantics that #foo  
might have.

> **···**
>
> --  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

---

<div class="post-metadata">

**Author:** ![Martin\_DeMello1](https://avatars.discourse-cdn.com/v4/letter/m/3be4f8/32.png) [@Martin\_DeMello1](https://rubytalk.org/u/Martin_DeMello1)\
**Post date:** [7 February 2006 12:48 UTC](https://rubytalk.org/t/lazy-evaluation/24691/5 "2006-02-07T12:48:21Z")

</div>

This seems wrong to me, for precisely the reason you cite - it means  
that I can never do something like object.foo(ary.pop), without  
physically reading the source of #foo to see whether it calls retry  
anywhere.

martin

> **···**
>
> vidar.hokstad@gmail.com \<vidar.hokstad@gmail.com\> wrote:
> 
> > It's your assumption of where the "retry" restarts execution that's  
> > wrong... Essentially the retry in "repeat" will restart execution right  
> > before the call to repeat, not at the start of the "repeat" method,  
> > thus evaluating the arguments again.
> > 
> > You can see that by changing your code so that there's an observable  
> > side effect of the reevaluation:

---

<div class="post-metadata">

**Author:** ![Vidar\_Hokstad](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vidar\_Hokstad](https://rubytalk.org/u/Vidar_Hokstad)\
**Post date:** [7 February 2006 13:58 UTC](https://rubytalk.org/t/lazy-evaluation/24691/6 "2006-02-07T13:58:23Z")

</div>

Martin DeMello wrote:

> \>  
> \> It's your assumption of where the "retry" restarts execution that's  
> \> wrong... Essentially the retry in "repeat" will restart execution right  
> \> before the call to repeat, not at the start of the "repeat" method,  
> \> thus evaluating the arguments again.  
> \>  
> \> You can see that by changing your code so that there's an observable  
> \> side effect of the reevaluation:
> 
> This seems wrong to me, for precisely the reason you cite - it means  
> that I can never do something like object.foo(ary.pop), without  
> physically reading the source of #foo to see whether it calls retry  
> anywhere.

Run the code I posted and see. Or see the reply with an example using  
tracing posted about the same time as my reply. Or to take any example  
using pop:

def repeat(cond)  
&nbsp;&nbsp;return if !cond  
&nbsp;&nbsp;yield  
&nbsp;&nbsp;retry  
end

ary = [1,2,3,4,5]

repeat(ary.pop) do  
&nbsp;&nbsp;p ary.size  
end

In any case, though, regardless of what method you call you would  
presumably want to know what it does before you use it, whether by  
reading the source or reading documentation, or relying on the method  
to be sanely named.

I just don't see how this behaviour makes any difference in terms of  
what surprises a badly documented and/or badly named method might have  
in store for you. After all, nothing stops anyone from doing all kinds  
of other completely unexpected things to your environment in some  
innocent sounding method - "retry" doesn't exactly add a lot to their  
arsenal, and it makes it a lot easier to create your own control  
structures.

Vidar

> **···**
>
> > vidar.hokstad@gmail.com \<vidar.hokstad@gmail.com\> wrote:

---

<div class="post-metadata">

**Author:** ![Jeff\_Schwab](https://avatars.discourse-cdn.com/v4/letter/j/b5ac83/32.png) [@Jeff\_Schwab](https://rubytalk.org/u/Jeff_Schwab)\
**Post date:** [7 February 2006 13:58 UTC](https://rubytalk.org/t/lazy-evaluation/24691/7 "2006-02-07T13:58:23Z")

</div>

Martin DeMello wrote:

> **···**
>
> > vidar.hokstad@gmail.com \<vidar.hokstad@gmail.com\> wrote:
> > 
> > > It's your assumption of where the "retry" restarts execution that's  
> > > wrong... Essentially the retry in "repeat" will restart execution right  
> > > before the call to repeat, not at the start of the "repeat" method,  
> > > thus evaluating the arguments again.
> > > 
> > > You can see that by changing your code so that there's an observable  
> > > side effect of the reevaluation:
> > 
> > This seems wrong to me, for precisely the reason you cite - it means  
> > that I can never do something like object.foo(ary.pop), without  
> > physically reading the source of #foo to see whether it calls retry  
> > anywhere.
> 
> That's like saying you have to read the source of every function before using it, just to make sure it's not going to start deleting things from your hard drive. Retry is a design tool that is appropriate in specific situations; it is not inserted at random in library functions.
> 
> It should be safe to trust methods not to 'retry' unless you have reason to believe otherwise.

---

<div class="post-metadata">

**Author:** ![HAL\_9000](https://avatars.discourse-cdn.com/v4/letter/h/90ced4/32.png) [@HAL\_9000](https://rubytalk.org/u/HAL_9000)\
**Post date:** [8 February 2006 01:05 UTC](https://rubytalk.org/t/lazy-evaluation/24691/8 "2006-02-08T01:05:27Z")

</div>

Martin DeMello wrote:

> **···**
>
> > vidar.hokstad@gmail.com \<vidar.hokstad@gmail.com\> wrote:
> > 
> > > It's your assumption of where the "retry" restarts execution that's  
> > > wrong... Essentially the retry in "repeat" will restart execution right  
> > > before the call to repeat, not at the start of the "repeat" method,  
> > > thus evaluating the arguments again.
> > > 
> > > You can see that by changing your code so that there's an observable  
> > > side effect of the reevaluation:
> > 
> > This seems wrong to me, for precisely the reason you cite - it means  
> > that I can never do something like object.foo(ary.pop), without  
> > physically reading the source of #foo to see whether it calls retry  
> > anywhere.
> 
> I never thought of it that way. I always thought of it in the context  
> of reevaluating an expression whose value might have changed -- but  
> side effects never occurred to me.
> 
> Hal
