4

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.

tadman
  • 208,517
  • 23
  • 234
  • 262
Inot
  • 103
  • 2
  • 8
  • "is there any form to recover that kind of use" Sorry, it's unclear: what was it that you wanted `retry` to accomplish? – matt May 08 '19 at 16:30
  • Sorry, I just wonder if it could be used to get the same result that it had in the first example, before being forced to be inside a "begin / rescue" block. Which seems to be, for me, being able to recall the method with an updated parameter. – Inot May 08 '19 at 16:35
  • The first example does not work in Ruby 2.6. You get an error on line 4: Invalid retry (SyntaxError) – tadman May 08 '19 at 16:36
  • @tadman I know, I found that it was forced to be inside of a rescue block in Ruby 2.3.1 (before that, it worked) – Inot May 08 '19 at 16:42

2 Answers2

4

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.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Thanks! It was really explanatory. So I could do something like this: `def WHILE(cond) begin return if not cond.call yield raise rescue retry end end i=0; WHILE(-> {i<3}) {print i; i+=1}` I know that this is pretty ugly and it's better to use a regular loop, but now I'm sure that my thoughs were for the right path. – Inot May 10 '19 at 13:23
  • So the method's parameter just needed to be "updated" (or reevaluated) for its evaluation at the return statement. And the retry keyword seemed to do that before 2.3.1. – Inot May 10 '19 at 13:34
  • You need what's loosely called a "dynamic" value, one that can be repeatedly evaluated to test against changing conditions. A lambda (Proc) closure does this job nicely, hence the first argument. They did change how `retry` worked in Ruby rather dramatically I think to avoid it doing two different and somewhat contradictory things depending on context. – tadman May 10 '19 at 16:34
0

IMHO, using loop defeats a bit the goal of the given exercise, hence I have two other solutions using redo or retry and no loop which I'd like to share.

If you have no idea how I may have done that I suggest you try before looking at the solutions.

1. retry

def _while(cond, _loop=Class.new(RuntimeError))
  raise _loop if cond.call
rescue _loop
  yield
  retry
end

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

2. redo

def _while(cond)
  proc do
    if cond.call
      yield
      redo
    end
  end.call
end

i = 0
_while(-> { i < 3 }) do
  print i
  i += 1
end
Ulysse BN
  • 10,116
  • 7
  • 54
  • 82