views:

394

answers:

5

I'm trying to use Ruby 1.9.1 for an embedded scripting language, so that "end-user" code gets written in a Ruby block. One issue with this is that I'd like the users to be able to use the 'return' keyword in the blocks, so they don't need to worry about implicit return values. With this in mind, this is the kind of thing I'd like to be able to do:

def thing(*args, &block)
  value = block.call
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

If I use 'return' in the above example, I get a LocalJumpError. I'm aware that this is because the block in question is a Proc and not a lambda. The code works if I remove 'return', but I'd really prefer to be able to use 'return' in this scenario. Is this possible? I've tried converting the block to a lambda, but the result is the same.

+1  A: 

Where is thing invoked? Are you inside a class?

You may consider using something like this:

class MyThing
  def ret b
    @retval = b
  end

  def thing(*args, &block)
    implicit = block.call
    value = @retval || implicit
    puts "value=#{value}"
  end

  def example1
    thing do
      ret 5 * 6
      4
    end
  end

  def example2
    thing do
      5 * 6
    end
  end
end
giorgian
+2  A: 

You are looking it from the wrong point of view. This is an issue of thing, not the lambda.

def thing(*args, &block)
  block.call.tap do |value|
    puts "value=#{value}"
  end
end

thing {
  6 * 7
}
Simone Carletti
+1  A: 

You cannot do that in Ruby.

The return keyword always returns from the method or lambda in the current context. In blocks, it will return from the method in which the closure was defined. It cannot be made to return from the calling method or lambda.

The Rubyspec demonstrates that this is indeed correct behaviour for Ruby (admiddedly not a real implementation, but aims full compatibility with C Ruby):

describe "The return keyword" do
# ...
describe "within a block" do
# ...
it "causes the method that lexically encloses the block to return" do
# ...
it "returns from the lexically enclosing method even in case of chained calls" do
# ...
molf
A: 

Thanks for the feedback, you guys are fast! As to giorgian's question, the code that I supplied is a complete self-contained script. ie, it's not invoked from anywhere else. As to marcgg's question, it's a customer requirement, it wasn't my idea :-)

Also, I think that molf has confirmed my suspicions, that it simply can't be done. The explanation that molf gave is what I had feared, but now I'm convinced. Once again, thanks all!

MetaFu
This is not an answer.
blinry
Seeing as he doesn't have 50 points to leave a comment, voting him down is clearly the right thing to do. Nice job.
Beanish
@Beanish: New users are able to comment on their own questions and answers. http://meta.stackoverflow.com/questions/1263/new-user-comment-clarification However, downvoting a n00b for this mistake is a bit harsh.
Andrew Grimm
@MetaFu: Your comment "Thanks for the feedback ..." shouldn't be an "answer", but it should be either 1) at the end of your question, 2) a comment underneath your question, or 3) a comment in reply to one of the answers.
Andrew Grimm
+4  A: 

Simply use next in this context:

$ irb
irb(main):001:0> def thing(*args, &block)
irb(main):002:1>   value = block.call
irb(main):003:1>   puts "value=#{value}"
irb(main):004:1> end
=> nil
irb(main):005:0>
irb(main):006:0* thing {
irb(main):007:1*   return 6 * 7
irb(main):008:1> }
LocalJumpError: unexpected return
        from (irb):7:in `block in irb_binding'
        from (irb):2:in `call'
        from (irb):2:in `thing'
        from (irb):6
        from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>'
irb(main):009:0> thing { break 6 * 7 }
=> 42
irb(main):011:0> thing { next 6 * 7 }
value=42
=> nil
  • return always returns from method, but if you test this snippet in irb you don't have method, that's why you have LocalJumpError
  • break returns value from block and ends its call. If your block was called by yield or .call, then break breaks from this iterator too
  • next returns value from block and ends its call. If your block was called by yield or .call, then next returns value to line where yield was called
MBO
nicely said. well done.
Nadal