Search code examples
rubyiteratorretry-logic

How does the retry statement work in Ruby?


I'm just getting started in Ruby, but the manual I'm following seems to be outdated. I've been doing some research but I've been unable to find a clear answer.

The manual used the 'retry' keyword inside of a method that should act as a loop, but it is used without a begin/rescue block, and the behavior seems pretty different when you force the 'retry' keyword to be inside of a begin/rescue block.

I've tried many things: -The first one was using the "begin" keyword at the start of the method and a 'raise' at the retry point, followed by the respective 'rescue; retry; end;'. -The last one was using the 'begin/rescue' block outside, wrapping the call of the method.

Following the logic in the manual, only the last one worked as it should.

There some examples:

The code in the manual is as follows:

def WHILE(cond)
  return if not cond
  yield
  retry
end
i=0; WHILE(i<3) {print i; i+=1}

result: 012

I've tried the following:

def WHILE(cond)
  begin
    return if not cond
    yield
    raise
  rescue
    retry
  end
end
i=0; WHILE(i<3) {print i; i+=1}

result: infinite numbers

def WHILE(cond)
  return if not cond
  yield
end
i=0; begin; WHILE(i<3) {print i; i+=1}; raise; rescue; retry; end

result: 012 (plus an obvious infinite loop printing nothing)

I expect you from taking me out of this existential doubt, but here's MY conclusion.

Before the obligatory use of the begin/rescue block for the 'retry' keyword, it could be used in a way that it can't anymore, repeating the call of a method in spite of being inside of that method.

Now, it just jumps to the 'begin' statement.

But i'm unsure of this, and I need confirmation. And if so, is there any form to recover that kind of use?

Thanks.


Solution

  • Your WHILE does not behave like a regular while since in your case i<3 is evaluated at call time once. The while statement evaluates it each time.

    If you want to write a while equivalent it's important that your condition be something that can be evaluated, not something that is already evaluated.

    You can fix that by accepting a Proc as a condition:

    def so_long_as(cond)
      loop do
        return unless cond.call
    
        yield
      end
    end
    

    Then you call it like this:

    i = 0
    so_long_as(-> { i < 3 }) do
      print i
      i += 1
    end
    

    Where that now prints 012 and terminates properly.

    What's important to note is that retry only works within a begin/end context, not a regular method, that now you have to use redo instead:

    i = 0
    redone = false
    so_long_as(-> { i < 3 }) do
      print i
      unless (redone)
        redone = true
        redo
      end
      i += 1
    end
    

    Where that prints 0012 now.

    There's been some significant changes to how redo and retry work that are worth reading up on.