Anyone care to comment on the analysis of 'retry' there? He presents the following code for a do-it-yourself while loop:
def my_while(cond) break unless cond yield retry end
i = 0 my_while i < 10 do print i i += 1 end
# output: 0123456789
And offers this analysis:
"This is where the Pickaxe II let me down. It said, "retry will reevaluate any arguments to the iterator before restarting it." Yes, clearly, that is what is happening. But how is it happening and what exactly does that simple English statement really mean?
So, after thinking about it, I concluded that what is going on is that a function call in Ruby works like this. Given a function f, a block b, and arguments xs, the call f(xs){b} means this:
1. let k be the current continuation (i.e., just before the call) 2. bind xs to f's formal arguments 3. bind b internally to the current block 4. evaluate the body of f
Now, if inside of f's body we encounter a retry, the evaluator basically calls k (with a nil argument, I expect). This jumps back to step 2, from which evaluation continues. Any side effects up to this point are retained (so we could have previously incremented i, for example), which is what allows the code within the function body eventually to choose an execution path which does not contain a retry expression, and thus avoid looping forever."
yeah, i was surprised too from: "I must say that I really like Ruby's semantics. So far, I find it to be a seriously cool programming language." I would'nt expect this static functional guys to like ruby ;)
> In article <1111806229.622292.217...@l41g2000cwc.googlegroups.com>, > Daniel Berger <djber...@hotmail.com> wrote: >>Maybe I need to rethink my view of Haskell after all:
> "This is where the Pickaxe II let me down. It said, "retry will > reevaluate > any arguments to the iterator before restarting it." Yes, clearly, that > is what is happening. But how is it happening and what exactly does that > simple English statement really mean?
> So, after thinking about it, I concluded that what is going on is that a > function call in Ruby works like this. Given a function f, a block b, and > arguments xs, the call f(xs){b} means this:
> 1. let k be the current continuation (i.e., just before the call) > 2. bind xs to f's formal arguments > 3. bind b internally to the current block > 4. evaluate the body of f
> Now, if inside of f's body we encounter a retry, the evaluator basically > calls k (with a nil argument, I expect). This jumps back to step 2, from > which evaluation continues. Any side effects up to this point are > retained (so we could have previously incremented i, for example), which > is what allows the code within the function body eventually to choose an > execution path which does not contain a retry expression, and thus avoid > looping forever."
I'm surprised about "retry", too. His analysis sounds all very resonable - only that "break" does not work for me but "return" does:
>> def my_while(cond) >> break unless cond >> yield >> retry >> end => nil >> i = 0 => 0 >> my_while i < 10 do ?> puts i >> i += 1 >> end
0 1 2 3 4 5 6 7 8 9 LocalJumpError: unexpected break from (irb):2:in `my_while' from (irb):7
>> def my_while(cond) >> return unless cond >> yield >> retry >> end => nil >> i = 0 => 0 >> my_while i < 10 do ?> puts i >> i += 1 >> end
0 1 2 3 4 5 6 7 8 9 => nil
ruby 1.8.1
Btw, can anybody think of a way to make my_while return the result of the last block evaluation? It seems impossible because the return occurs before the yield...
>I'm surprised about "retry", too. His analysis sounds all very resonable - >only that "break" does not work for me but "return" does:
>>> def my_while(cond) >>> break unless cond >>> yield >>> retry >>> end >=> nil >>> i = 0 >=> 0 >>> my_while i < 10 do >?> puts i >>> i += 1 >>> end >0 >1 >2 >3 >4 >5 >6 >7 >8 >9 >LocalJumpError: unexpected break > from (irb):2:in `my_while' > from (irb):7
Hmmm... break works find for me in 1.8.2. Time to upgrade?
>Btw, can anybody think of a way to make my_while return the result of the >last block evaluation? It seems impossible because the return occurs before >the yield...
>>I'm surprised about "retry", too. His analysis sounds all very resonable - >>only that "break" does not work for me but "return" does:
>>>> def my_while(cond) >>>> break unless cond >>>> yield >>>> retry >>>> end >>=> nil >>>> i = 0 >>=> 0 >>>> my_while i < 10 do >>?> puts i >>>> i += 1 >>>> end >>0 >>1 >>2 >>3 >>4 >>5 >>6 >>7 >>8 >>9 >>LocalJumpError: unexpected break >> from (irb):2:in `my_while' >> from (irb):7
>Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>Btw, can anybody think of a way to make my_while return the result of the >>last block evaluation? It seems impossible because the return occurs before >>the yield...
>>>I'm surprised about "retry", too. His analysis sounds all very resonable - >>>only that "break" does not work for me but "return" does:
>>>>> def my_while(cond) >>>>> break unless cond >>>>> yield >>>>> retry >>>>> end >>>=> nil >>>>> i = 0 >>>=> 0 >>>>> my_while i < 10 do >>>?> puts i >>>>> i += 1 >>>>> end >>>0 >>>1 >>>2 >>>3 >>>4 >>>5 >>>6 >>>7 >>>8 >>>9 >>>LocalJumpError: unexpected break >>> from (irb):2:in `my_while' >>> from (irb):7
>>Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>>Btw, can anybody think of a way to make my_while return the result of the >>>last block evaluation? It seems impossible because the return occurs before >>>the yield...
Oh, and my proposed my_while that returns the last block evaluation:
def my_while(cond) break @ret unless cond #NOTE: break can return a value @ret = yield retry end
wouldn't work if you have nested my_while's. You would have to create some kind of class that contains a while method in order for nesting to be handled correctly.
something like:
class Whiler def initialize @ret = nil end
def while(cond) break @ret unless cond @ret = yield retry end end
outer = Whiler.new inner = Whiler.new
i=0 outer.while i<5 do puts "i: #{i}" j=0 inner.while j<5 do puts " j: #{j}" j+=1 end i+=1 end
>>>I'm surprised about "retry", too. His analysis sounds all very resonable - >>>only that "break" does not work for me but "return" does:
>>>>> def my_while(cond) >>>>> break unless cond >>>>> yield >>>>> retry >>>>> end >>>=> nil >>>>> i = 0 >>>=> 0 >>>>> my_while i < 10 do >>>?> puts i >>>>> i += 1 >>>>> end >>>0 >>>1 >>>2 >>>3 >>>4 >>>5 >>>6 >>>7 >>>8 >>>9 >>>LocalJumpError: unexpected break >>> from (irb):2:in `my_while' >>> from (irb):7
>>Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>>Btw, can anybody think of a way to make my_while return the result of the >>>last block evaluation? It seems impossible because the return occurs before >>>the yield...
> In article <3alo9bF6d2vo...@individual.net>, > Robert Klemme <bob.n...@gmx.net> wrote:
>>I'm surprised about "retry", too. His analysis sounds all very >>resonable - >>only that "break" does not work for me but "return" does:
>>>> def my_while(cond) >>>> break unless cond >>>> yield >>>> retry >>>> end >>=> nil >>>> i = 0 >>=> 0 >>>> my_while i < 10 do >>?> puts i >>>> i += 1 >>>> end >>0 >>1 >>2 >>3 >>4 >>5 >>6 >>7 >>8 >>9 >>LocalJumpError: unexpected break >> from (irb):2:in `my_while' >> from (irb):7
> Hmmm... break works find for me in 1.8.2. Time to upgrade?
Yeah, maybe.
>>Btw, can anybody think of a way to make my_while return the result of the >>last block evaluation? It seems impossible because the return occurs >>before >>the yield...
>>>>I'm surprised about "retry", too. His analysis sounds all very >>>>resonable - >>>>only that "break" does not work for me but "return" does:
>>>>>> def my_while(cond) >>>>>> break unless cond >>>>>> yield >>>>>> retry >>>>>> end >>>>=> nil >>>>>> i = 0 >>>>=> 0 >>>>>> my_while i < 10 do >>>>?> puts i >>>>>> i += 1 >>>>>> end >>>>0 >>>>1 >>>>2 >>>>3 >>>>4 >>>>5 >>>>6 >>>>7 >>>>8 >>>>9 >>>>LocalJumpError: unexpected break >>>> from (irb):2:in `my_while' >>>> from (irb):7
>>>Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>>>Btw, can anybody think of a way to make my_while return the result of >>>>the >>>>last block evaluation? It seems impossible because the return occurs >>>>before >>>>the yield...
>>@ret may not be defined at that point of evaluation.
> Er, defined 'correctly', that is. It'll just be the > current value of i. Otherwise a novel approach!
What do you mean by "correctly"? If there's no value you'll get nil which is ok if the condition evaluates to false right from the start IMHO. The problem I see with this is rather thread safety (which could be handled by thread local variables) and nesting (which is a bit more difficult to cope with IMHO; I guess you'll need a stack of values as thread local or such).
>"Phil Tomson" <pt...@aracnet.com> schrieb im Newsbeitrag >news:d24b8d02fq1@enews4.newsguy.com... >> In article <3alo9bF6d2vo...@individual.net>, >> Robert Klemme <bob.n...@gmx.net> wrote:
>>>I'm surprised about "retry", too. His analysis sounds all very >>>resonable - >>>only that "break" does not work for me but "return" does:
>>>>> def my_while(cond) >>>>> break unless cond >>>>> yield >>>>> retry >>>>> end >>>=> nil >>>>> i = 0 >>>=> 0 >>>>> my_while i < 10 do >>>?> puts i >>>>> i += 1 >>>>> end >>>0 >>>1 >>>2 >>>3 >>>4 >>>5 >>>6 >>>7 >>>8 >>>9 >>>LocalJumpError: unexpected break >>> from (irb):2:in `my_while' >>> from (irb):7
>> Hmmm... break works find for me in 1.8.2. Time to upgrade?
>Yeah, maybe.
>>>Btw, can anybody think of a way to make my_while return the result of the >>>last block evaluation? It seems impossible because the return occurs >>>before >>>the yield...
We've been having this discussion about defining your own while loop in a thread about Haskell and I thought I'd change the subject in case people who had no interest in Haskell were avoiding it ;-)
>>"Phil Tomson" <pt...@aracnet.com> schrieb im Newsbeitrag >>news:d24b8d02fq1@enews4.newsguy.com... >>> In article <3alo9bF6d2vo...@individual.net>, >>> Robert Klemme <bob.n...@gmx.net> wrote:
>>>>I'm surprised about "retry", too. His analysis sounds all very >>>>resonable - >>>>only that "break" does not work for me but "return" does:
>>>>>> def my_while(cond) >>>>>> break unless cond >>>>>> yield >>>>>> retry >>>>>> end >>>>=> nil >>>>>> i = 0 >>>>=> 0 >>>>>> my_while i < 10 do >>>>?> puts i >>>>>> i += 1 >>>>>> end >>>>0 >>>>1 >>>>2 >>>>3 >>>>4 >>>>5 >>>>6 >>>>7 >>>>8 >>>>9 >>>>LocalJumpError: unexpected break >>>> from (irb):2:in `my_while' >>>> from (irb):7
>>> Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>Yeah, maybe.
>>>>Btw, can anybody think of a way to make my_while return the result of the >>>>last block evaluation? It seems impossible because the return occurs >>>>before >>>>the yield...
>>Not thread safe. And I think also not nesting safe.
>Yes, I know. Did you see my later post? I don't think it made it from >the newsgroup to the mailing list.
>Here's the code to make it nestable and threadsafe:
> class Whiler > def initialize > @ret = nil > end
> def while(cond) > break @ret unless cond > @ret = yield > retry > end > end
> outer = Whiler.new > inner = Whiler.new
> i=0 > outer.while i<5 do > puts "i: #{i}" > j=0 > inner.while j<5 do > puts " j: #{j}" > j+=1 > end > i+=1 > end
>A bit cumbersome, perhaps, but it is definately nestable and should be >threadsafe as well.
Actually, I figured out a way to make it much more natural:
Change Whiler to accept an optional condition in it's constructor like so:
class Whiler def initialize(cond=nil) @ret = nil @cond = cond end def while(cond=@cond) break @ret unless cond @ret = yield retry end end
Define the my_while top-level method like so:
def my_while(cond,&block) Whiler.new(cond).method(:while).call(&block) end
Now the user of my_while doesn't need to know that a Whiler class even exists. Usage example:
i = 0 my_while(i<10) do puts i j=0 my_while(j<10) do puts " j: #{j}" j+=1 end i+=1 end
I think there are some implications for DSLs (Domain Specific Languages) in this example. The my_while method as defined above (the last one) hides some Ruby language details from the user of my_while so that my_while can appear like a natural looping construct. I've created DSLs for people who had no idea they were using Ruby underneath; this sort of thing is necessary so they don't feel as though they are obliged to learn Ruby to use the DSL.
Another example of why Ruby is so good for creating DSLs. ;-)
> We've been having this discussion about defining your own while loop in a > thread about Haskell and I thought I'd change the subject in case people > who had no interest in Haskell were avoiding it ;-)
> In article <d26sfn01...@enews3.newsguy.com>, > Phil Tomson <pt...@aracnet.com> wrote: >>In article <3anjpeF6d91a...@individual.net>, >>Robert Klemme <bob.n...@gmx.net> wrote:
>>>"Phil Tomson" <pt...@aracnet.com> schrieb im Newsbeitrag >>>news:d24b8d02fq1@enews4.newsguy.com... >>>> In article <3alo9bF6d2vo...@individual.net>, >>>> Robert Klemme <bob.n...@gmx.net> wrote:
>>>>>I'm surprised about "retry", too. His analysis sounds all very >>>>>resonable - >>>>>only that "break" does not work for me but "return" does:
>>>>>>> def my_while(cond) >>>>>>> break unless cond >>>>>>> yield >>>>>>> retry >>>>>>> end >>>>>=> nil >>>>>>> i = 0 >>>>>=> 0 >>>>>>> my_while i < 10 do >>>>>?> puts i >>>>>>> i += 1 >>>>>>> end >>>>>0 >>>>>1 >>>>>2 >>>>>3 >>>>>4 >>>>>5 >>>>>6 >>>>>7 >>>>>8 >>>>>9 >>>>>LocalJumpError: unexpected break >>>>> from (irb):2:in `my_while' >>>>> from (irb):7
>>>> Hmmm... break works find for me in 1.8.2. Time to upgrade?
>>>Yeah, maybe.
>>>>>Btw, can anybody think of a way to make my_while return the result of >>>>>the >>>>>last block evaluation? It seems impossible because the return occurs >>>>>before >>>>>the yield...
>>Here's the code to make it nestable and threadsafe:
>> class Whiler >> def initialize >> @ret = nil >> end
>> def while(cond) >> break @ret unless cond >> @ret = yield >> retry >> end >> end
>> outer = Whiler.new >> inner = Whiler.new
>> i=0 >> outer.while i<5 do >> puts "i: #{i}" >> j=0 >> inner.while j<5 do >> puts " j: #{j}" >> j+=1 >> end >> i+=1 >> end
>>A bit cumbersome, perhaps, but it is definately nestable and should be >>threadsafe as well.
> Actually, I figured out a way to make it much more natural:
> Change Whiler to accept an optional condition in it's constructor like > so:
> class Whiler > def initialize(cond=nil) > @ret = nil > @cond = cond > end > def while(cond=@cond) > break @ret unless cond > @ret = yield > retry > end > end
> Define the my_while top-level method like so:
> def my_while(cond,&block) > Whiler.new(cond).method(:while).call(&block) > end
> Now the user of my_while doesn't need to know that a Whiler class even > exists. Usage example:
> i = 0 > my_while(i<10) do > puts i > j=0 > my_while(j<10) do > puts " j: #{j}" > j+=1 > end > i+=1 > end
This is nice!
> I think there are some implications for DSLs (Domain Specific Languages) > in this example. > The my_while method as defined above (the last one) hides some Ruby > language details from the user of my_while so that my_while can appear > like a > natural looping construct. I've created DSLs for people who had > no idea they were using Ruby underneath; this sort of thing is > necessary so they don't feel as though they are obliged to learn > Ruby to use the DSL.
> Another example of why Ruby is so good for creating DSLs. ;-)
Phil Tomson <pt...@aracnet.com> wrote: >In article <Pine.LNX.4.21.0503280111230.10981-100000@frieza>, >Relm <r...@3tlk.net> wrote: >>On Mon, 28 Mar 2005, Phil Tomson wrote:
>>> Actually, I figured out a way to make it much more natural:
>>> Change Whiler to accept an optional condition in it's constructor like >>> so:
>>> class Whiler >>> def initialize(cond=nil) >>> @ret = nil >>> @cond = cond >>> end >>> def while(cond=@cond) >>> break @ret unless cond >>> @ret = yield >>> retry >>> end >>> end
>>> Define the my_while top-level method like so:
>>> def my_while(cond,&block) >>> Whiler.new(cond).method(:while).call(&block) >>> end
>>This new my_while doesn't seem to return the last block. The following >>snippet outputs nil values instead of "outer" and "inner":
>> i = 0 >> x = nil >> y = nil
>> x = my_while(i<10) do >> j=0 >> y = my_while(j<10) do >> j+=1 >> "inner" >> end >> i+=1 >> "outer" >> end
>> puts x >> puts y
>Hmmm... I'm seeing the same thing you are, but I'm not sure why. >Anyone got any ideas?
Ah, I see the problem now. my_while calls Whiler.new every time it is called of course, thus creating a new Whiler object each time. The last time through each loop, the condition is false when Whiler.new is called and thus @ret is nil because my_while returns before getting to the '@ret = yield'. Definately problematic. You can see the problem by adding a 'puts' to the Whiler constructor like so:
class Whiler def initialize(cond=nil) puts "Whiler::new(#{cond})" @ret = nil @cond = cond end end
>>This makes your original my_while thread-safe. It was already safe for >>nesting.