views:

400

answers:

2

In Ruby 1.8 (my version is 1.8.7-72), this code:

foo = lambda do
  for j in 1..2
    return
  end
end
foo.call

crashes with a LocalJumpError:

test2.rb:3: unexpected return (LocalJumpError)
    from test2.rb:2:in `each'
    from test2.rb:2
    from test2.rb:6:in `call'
    from test2.rb:6

Why does it do this? However, it seems to run fine on my version of Ruby 1.9.

Edit: it's not just the returning inside a lambda; the following runs fine:

foo = lambda do
  return
end
foo.call
+6  A: 

What's happening is that the for statement in the middle of the lambda is converted internally into a block. In Ruby, return statements inside blocks are scoped to their enclosing method. Consider the following:

def bar
  foo = lambda do
    for j in 1..2
      return j
    end
  end
  foo[]
end
p bar

When running bar, 1 is returned, because the return is scoped to the entire bar method. To return from blocks, you want to use next or break, both of which take parameters. Consider:

def bar
  foo = lambda do
    for j in 1..2
      break j
    end
  end
  foo[] + 1
end
p bar

This break returns you from the block, and blocks any subsequent iterations. In this case, calling bar would return 2, since the iterator will return 1, and foo[] + 1 will therefore return 2.

If all of that sounded confusing, the main thing to realize is that return inside blocks is scoped to a surrounding method, and absent a surrounding method, a LocalJumpError is raised.

Yehuda Katz
why can't it scope to the lambda instead? I can't simply break out of the loop, because in my original scenario, there is code after the for loop which I need to skip
You could try using a real method, rather than a lambda.
Yehuda Katz
Damn. Ruby 1.8 won't allow return from inside a proc from inside a method defined via define_method. Scratch that.
Yehuda Katz
A: 

you can escape the loop and the rest of the lambda with a throw/catch

foo = lambda do 
        catch(:escape) do
          for j in 1..2
            throw :escape
          end
          # other code that won't get run
        end # catch(:escape)
      end # lambda

throw also takes an optional second paramter, which you can use to return a value.

Logan Capaldo
This is a pretty heavyweight solution to emulate a return, I think.
Yehuda Katz
there aren't a lot of other options wrt to _emulating_ a return (the other options being exceptions and callcc). I suspect of the choices it is the lightest weight. The best solution is probably just use a method, but this can be awkward if the scope you need to close over doesn't lend itself well to that. This is why 1.9 letting you return from lambda's is nice.
Logan Capaldo
I added a way to emulate Ruby 1.9 lambdas in Ruby 1.8 in my answer, which would be more lightweight than throw/catch.
Yehuda Katz
Hmm. I tried that approach, I didn't quite get it to work. I'm no doubt rusty.
Logan Capaldo
Nah. Ruby 1.8 is old and busted and it won't work :( I reverted my change.
Yehuda Katz