56

What is the problem with this Ruby 2.0 code?

p (1..8).collect{|denom|
    (1...denom).collect{|num|
        r = Rational(num, denom)
        if r > Rational(1, 3) and r < Rational(1, 2)
            return 1
        else
            return 0
        end
    }
}.flatten

The error is in block (2 levels) in <main>': unexpected return (LocalJumpError). I want to create a flat list containing n ones (and the rest zeroes) where n is the number of rational numbers with denominators below 8 which are between 1/3 and 1/2. (it's a Project Euler problem). So I'm trying to return from the inner block.

Eli Rose
  • 6,788
  • 8
  • 35
  • 55

1 Answers1

59

You can't return inside a block in Ruby*. The last statement becomes the return value, so you can just remove the return statements in your case:

p (1..8).collect{|denom|
    (1...denom).collect{|num|
        r = Rational(num, denom)
        if r > Rational(1, 3) and r < Rational(1, 2)
            1
        else
            0
        end
    }
}.flatten

*: You can inside lambda blocks: lambda { return "foo" }.call # => "foo". It has to do with scoping and all that, and this is one of the main differences between lambda blocks and proc blocks. "Normal" blocks you pass to methods are more like proc blocks.

sarahhodne
  • 9,796
  • 3
  • 39
  • 44
  • 102
    You *can* use `return` inside a block in Ruby. It will return from the enclosing method. In this case, there *is no* enclosing method, *that's* why there is an error, it's *not* because `return` in a block is illegal. – Jörg W Mittag Jul 23 '13 at 05:14
  • @JörgWMittag You're right, I was simplifying. `return` returns for the nearest method *or* `lambda` block that you're inside. If you're not inside any, you'll get a `LocalJumpError`. – sarahhodne Jul 23 '13 at 05:16
  • @JörgWMittag thanks for this clarifying statement. The answer was confusing as-is and you clarified it. I understand now why this error comes up in irb easily. – aaron-coding Oct 16 '15 at 19:29
  • [i feel] this answer definitely needs to be updated with @JörgWMittag's comment because the answer is still technically wrong. I would appreciate if one of you could go in and re-explain the answer with "enclosing method" explained as well with an example of what would work and what wouldn't. – Govind Rai Feb 13 '17 at 23:26
  • @JörgWMittag care for a snippet? – OK999 Dec 13 '17 at 21:00
  • 1
    @OK999: `def foo; yield end; def bar; foo { return } end; bar` – Jörg W Mittag Dec 13 '17 at 21:33
  • [Starting with Ruby 2.4](https://github.com/ruby/ruby/blob/750f1a39882910dd6357550e2187c264e3d7f0b5/doc/NEWS-2.4.0#language-changes) one can use `return` outside of a method. Feature request is [here](https://bugs.ruby-lang.org/issues/4840). – sshaw Jun 05 '18 at 21:35
  • 1
    The phrase @JörgWMittag used "it will return from the enclosing method" is open to misunderstanding. It returns from the method where the block was CREATED not, as many people assume, from the method where the block was CALLED. – ComDubh May 21 '20 at 07:07
  • @ComDubh: Normally, when we say "enclosing", we mean "lexically enclosing". (That is really the only sensible interpretation.) The method where the block was called, doesn't lexically enclose the `return`, in fact, it has no syntactic relationship whatsoever (the relationship is purely dynamic). – Jörg W Mittag May 21 '20 at 09:06
  • 1
    @JörgWMittag Agreed, but many developers think that if, during the execution of `foo(a_proc)`, `a_proc` returns, this will cause `foo` to return. Of course it won't, it will cause the method (or lambda body) enclosing the *creation* of `a_proc` to return. Perhaps you don't agree this is a common misunderstanding? I've seen it several blogs and SO answers. – ComDubh May 21 '20 at 12:09
  • @ComDubh Definitely a common misunderstanding, in fact it's why I came to this discussion – Joel Blum Mar 20 '22 at 09:21