header image

redo.pngI spent the first 2+ years in Ruby-land without even knowing about the probably most underused (and underrated) keyword of the language: redo. Even after I came across the first example and liked it immensely, I could not come up with another use for it, so I threw it to the bottom of my toolbox. Then I found another example, and another one - so I came to the conclusion that redo might be a valuable keyword in your Ruby arsenal after all - it is one of those things which you rarely need, but if you need it, it’s a perfect solution which would be cumbersome to replace with other constructs.

Break vs Next vs Redo

I am sure everyone is familiar with redo’s cousins: next and break. While next and break allow you to skip an iteration, resp. skip the rest of the remaining iterations altogether, redo does not skip anything - it ‘restarts’ the same iteration. In other words, while next transfers control to the end of the iteration block, redo ‘jumps’ to the beginning of the block. Let’s see an example, where redo is used to recover from an input error (code from “The Ruby Programming Language”):

  1. puts "Please enter the first word you can think of"
  2. words =%w(apple banana cherry)
  3. response = words.collect do |word|
  4.   print word + "> "
  5.   response = gets.chop
  6.   if response.size == 0
  7.     word.upcase!
  8.     redo
  9.   end
  10.   response
  11. end

The trick is that whenever the user enters an empty string, the block is restarted with redo, asking the same ‘question’ until the user enters something that makes sense to the system.

A Real Example - Tail Recursion

As of now, Ruby does not support tail recursion optimization, so you either have to write the function in a non-recursive (i.e. iterative) way yourself, or suffer the consequences of a full stack… or use redo!

I recently came across Magnus Holm’s article on this problem. Magnus provides an elegant solution using redo:

  1. define_method(:acc) do |i, n, result|
  2.   if i == -1
  3.     result
  4.   else
  5.     i, n, result = i - 1, n + result, n
  6.     redo
  7.   end
  8. end
  9.  
  10. def fib(i)
  11.   acc(i, 1, 0)
  12. end

Magnus even worked around a redo perk: “The redo statement restarts the current iteration of a loop or iterator”. That means he couldn’t define a method with “def”, since that neither involves a loop nor an iterator. Maybe a less esoteric way relying on the same fact would be to use a lambda:

  1. def fib(i)
  2.   acc = lambda do |i, n, result|
  3.     if i == -1
  4.       result
  5.     else
  6.       i, n, result = i - 1, n + result, n
  7.       redo
  8.     end
  9.   end.call(i, 1, 0)
  10. end

Golf Time

Here is a Ruby golf riddle, which I have no idea how could be made this short (42 characters) without redo (via coderrr):

"Generate a random string of length 4 or more consisting of only the following 
set of characters: a-z, A-Z, 0-9"

Here is the winner, Magnus Holm’s solution (The name sounds familiar? Right, the previous example was from him too - he understands the power of redo for sure :-)).

  1. (0..9).map{rand(?z).chr[/[^_\W]/]||redo}*""

The above solution might not be instantly crystal clear - so let’s break it down a bit:

  1. rand(?z) is equivalent to rand(122) (but shaves off a character!)
  2. rand(?z).chr turns the random number into a string (equivalent to its ASCII code, i.e. 65.chr == “A” etc)
  3. the regexp /[^_\W]/ means the negation of ‘everything but underscore or non-word character (digits and numbers). To put it another way, accept word characters but not an underscore. (\w includes underscore, so we need to get rid of that)
  4. rand(?z).chr[/[^_\W]/] acts as an identity function for letters and digits, but returns nil for everything else. So “a”[/[^_\W]/] == “a” but “@”[/[^_\W]/] == nil. “_”[/[^_\W]/] also returns nil.
  5. And now comes the redo trick: rand(?z).chr[/[^_\W]/] || redo will restart the block until a random character/letter is not found! w00t!

While one might argue that golfing examples are contrived by definition, I don’t always agree: the [/[^_\W]/] is a neat trick to get numbers/letters from a string only, as well as the use of redo is valid and non-contrived.

Conclusion

As demonstrated above, redo can be used in a variety of situations to produce an elegant solution that would look much more cumbersome without it. Of course all the usual stuff applies: don’t overuse, abuse etc. etc.

If you have any other interesting cases for redo, leave a comment - I’d love to see some more :-)



If you liked the article, subscribe to the feed   and follow me on twitter!.


      

28 Responses to “Ruby’s Most Underused Keyword”

  1. Avdi Says:

    Great article on a truly obscure corner of Ruby!

  2. David Brady Says:

    I recently needed to do a complex iterate/delete sequence: given an array of chains, delete all the chains that are contained in other chains. Normally I’d attack this with recursion, but I had just seen the redo tail-recursion article and wondered if it was possible to do using redo.

    If you delete an element while iterating, say at step 4, element 5 will move up to step 4’s place. When you iterate to the next number, 5, you will have skipped the element that just moved up to 4. My final code ended up with this nugget in it:

    chains.delete_at(i)
    chain = chains[i]
    redo unless chain.nil?

    It works quite nicely. :-)

  3. JeffMo Says:

    Your code sample appears to have a minor transcription error. The final character should be replaced with two quotation marks (single or double will work).

  4. JeffMo Says:

    Sorry, I meant to call out the “golf” sample in my last comment.

  5. peter Says:

    Thanks Jeff, fixed. In the code it was correct btw (two single quotation marks) but the code highlighter decided to mess it up ;) Replacing the single quotes with double ones helped…

  6. Bazza Says:

    “redo” is another Perlism in Ruby (http://perldoc.perl.org/functions/redo.html).

    You don’t see it often used in Perl either… despite it being a bit more powerful (you can redo loops higher up the stack by using “redo LABEL”.

  7. Sean Huber Says:

    Awesome. I had never heard of redo.

  8. Phil Says:

    Redo would be about 5x more awesome if it took arguments that would become the new block parameters. The “assign, then redo” idiom is a bit ugly.

    Still, a good trick to be aware of.

  9. Argus Panoptes Says:

    Thank you for this very helpful article!

  10. Julian Says:

    I do in C style languages like

    for (var i=1;i<10;i++)
    {
    snibble();
    snout(i);
    if(youwantto_redo==true)
    {i=i-1;}
    }

  11. Peter Cooper Says:

    The fib method (both Magnus’s and yours) return wrong results. It gives me fib(10) => 89, but fib(10) == 55. I see this error quite a lot when people try to write Fibonacci number functions, not sure why. Chart with results for verification: http://en.wikipedia.org/wiki/Fibonacci_number

    Anyway, Magnus noted that the performance difference between the redo version and the “iterative” version is almost nothing. That’s because the redo version is an iterative solution.. it’s just using a different tactic (a bit like using a while loop to count instead of a for loop.. same result, just some different keywords).

    If you strip out all the cruft, you end up with this:

    def fib(i)
    n, result = 1, 0
    while i != -1
    i, n, result = i - 1, n + result, n
    end
    result
    end

    .. which is clearly iterative (note: I’ve implemented this to match the results of your methods).

    While redo is pretty cool, the whole Fibonacci example seems bogus to me. redo is not a third way, in this situation it’s just a crufty way of producing an iterative solution.

  12. peter Says:

    PeterC: Hehe, it’s so funny when one is blogging about some obscure advanced feature and he doesn’t get fibonacci right ;-) even if its me in this case :-) (though I just copied Magnus’s solution and wrapped into a lambda - I ran it, but since I don’t know fib(1000) by heart, it was not apparent that it’s not working :-)).

    But yeah, I get your point and you are right to some extent, but I still think the example makes sense if you are viewing the progress how we get there - Magnus’s article is much better in this regard, since it tries to simulate tail recursion optimization and arrives at redo. If the original question would have been ‘is it possible to implement fibonacci numbers iteratively’, no one would even think about reaching after redo. However, if the question is to write ‘tail optimizable recursion’ (ok, I dig it that this was kind of a non-sense :-) I think the solution is valid.

    Anyway, AFAIK 1.9 does tail recursion optimization, so it doesn’t matter any more.

  13. Magnus Holm Says:

    PeterC: Frankly, I don’t care if it’s broken, that wasn’t my point :-)

    From an interpreter’s point of view, you are completely right. That’s why it’s so fast. However, from a user’s POV it’s not an iterative solution. The redo simply says “run this block (aka method) again”; exactly like acc() would do.

  14. Peter Cooper Says:

    [..] from a user’s POV it’s not an iterative solution. The redo simply says “run this block (aka method) again” [..]

    So redo is being used to implement a loop (i.e. run this [whatever] again). That’s iterative, right? :) It’s a kinda cool experiment to do loops that way, but, of course, there are already nicer/faster ways to loop, such as while..end, loop..end, iterators, and so forth. Using redo in this way is a modern equivalent of “goto”.

    Or are you suggesting the average coder would look at that implementation with redo and mistakenly believe it was recursive? I’d really like to hope they wouldn’t, but you never know!

    Anyway, AFAIK 1.9 does tail recursion optimization, so it doesn’t matter any more.

    Unfortunately not. From vm_opts.h on Ruby 1.9.1-p0:

    define OPTTAILCALLOPTIMIZATION 0

    However, a comment in the source claims you can turn it on where necessary (although it is claimed odd bugs can emerge) using VM::CompileOption, but I am not familiar with this.

  15. Huy Says:

    Redo screams goto.

  16. peter Says:

    Huy: not at all - following this logic you can say the same about next or break. Yeah, as PeterC stated above, it can be used as a kind of goto in some cases, but in general, that’s not true.

  17. JeffMo Says:

    Huy: I agree with peter on this one. All jumps or branches or looping or subroutine/function/method calls and returns (and many other language idioms) can scream “goto,” if you squint hard enough.

    The main historically recognized problem with goto was that it was too generic. Language designers come up with richer, more expressive, and more specific ways to indicate the flow of execution, and gotos become unnecessary and counterproductive. Redo is way more specific and expressive than goto, and as peter mentions, is essentially just as specific as next and break.

  18. JeffMo Says:

    Also, I wanted to thank Peter Szinek for mentioning this specific Ruby golf problem. I had a great deal of fun playing with it this morning, and posted about it over at coderrr.

  19. Magnus Holm Says:

    JeffMo: Agreed. Methods, conditions, loops, iterators, break, next and redo; they’re all gotos on crack.

  20. peter Says:

    JeffMo: Oh yeah, the Ruby golf problem rocks. Only Magnus’s solution rocks even more ;)

  21. Rick Says:

    Is it like, continue (in C)? If it is, then it’s awesome.

  22. peter Says:

    No, it’s better than that ;-) The equivalent of continue is next; there is no redo() equivalent in C.

  23. tieTYT Says:

    I think all 3 of these keywords are bad and here’s why: It makes it hard to refactor your code. Look at this pseudo code:

    for (…)

    if done

    break
    end if
    if goToNext

    next
    end if
    if restartBlock

    redo
    end if

    }

    Now, let’s say the “if done” block gets so big that you want to extract a method out of it:

    myIfDoneMethod

    break
    end method

    Oops, this won’t work… “break” makes no sense in a method without a loop. So now you’ve gotta change the loop and the method to have a condition that simulates “break”. If you wrote your loop with the condition in the first place, you wouldn’t have to deal with this.

  24. Ennuyer.net » Blog Archive » 2009-02-08- Today’s Ruby/Rails Reading Says:

    [...] Ruby’s Most Underused Keyword [...]

  25. Marko Says:

    Here’s how I once used redo. To generate a feed of user’s activity, I wanted to have an item like “posted 3 videos: x y z”. In order to achieve that, I’d loop over a mixed up array of all kinds of activities… Once I begin catching videos, I’d detect hitting a non-video item, render the partial for them and redo.

  26. citizen428.blog() Says:

    Some quick Ruby tips…

    Good news for OS X users: Priit Haamer provides documentation for Ruby 1.9 and Rails as dictionaries for Dictionary.app, which makes them searchable by Spotlight (Ruby documentation, Rails documentation!#
    
    There also was a nice discussion over the la...
    
  27. Ruby, Rails, Web2.0 » Blog Archive » Ruby’s Most Underused Keyword | Web2.0时代 Says:

    [...] the original here:  Ruby, Rails, Web2.0 » Blog Archive » Ruby’s Most Underused Keyword argus-panoptes, attending, block, code, february-6th, magnus, news, peter, peter-cooper, redo, [...]

  28. Bookmarks for marzo 11th from 02:00 to 02:00 | Bloggerman Says:

    [...] Ruby, Rails, Web2.0 » Blog Archive » Ruby’s Most Underused Keyword – [...]

Leave a Reply




Bad Behavior has blocked 1031 access attempts in the last 7 days.