在Ruby块中使用'return'

时间:2010-02-24 11:15:41

标签: ruby lambda return proc-object

我正在尝试将Ruby 1.9.1用于嵌入式脚本语言,以便“最终用户”代码在Ruby块中编写。这个问题的一个问题是我希望用户能够在块中使用'return'关键字,因此他们不需要担心隐式返回值。考虑到这一点,我希望能够做到这一点:

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

thing {
  return 6 * 7
}

如果我在上面的例子中使用'return',我会得到一个LocalJumpError。我知道这是因为有问题的块是Proc而不是lambda。如果我删除'return',代码就可以工作,但我真的更喜欢在这种情况下使用'return'。这可能吗?我已经尝试将块转换为lambda,但结果是一样的。

8 个答案:

答案 0 :(得分:161)

在此上下文中只需使用next

$ 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始终从方法返回,但如果您在irb中测试此代码段,则表示您没有方法,这就是您拥有LocalJumpError
  • 的原因
  • break从块返回值并结束其调用。如果您的块被yield.call调用,那么break也会从此迭代器中断
  • next从块返回值并结束其调用。如果您的阻止由yield.call调用,则next会将值返回到调用yield的行

答案 1 :(得分:17)

你不能在Ruby中做到这一点。

return关键字始终从当前上下文中的方法或lambda返回。在块中,它将从闭包定义的方法返回。无法从调用方法或lambda。

返回

Rubyspec表明这确实是Ruby的正确行为(诚然不是真正的实现,但与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
# ...

答案 2 :(得分:3)

你是从错误的角度来看待它。 这是thing的问题,而不是lambda。

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

thing {
  6 * 7
}

答案 3 :(得分:1)

调用的是哪里?你在课堂上吗?

您可以考虑使用以下内容:

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

答案 4 :(得分:1)

我认为这是正确的答案,尽管存在缺点:

def return_wrap(&block)
  Thread.new { return yield }.join
rescue LocalJumpError => ex
  ex.exit_value
end

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

thing {
  return 6 * 7
}

这个hack允许用户在他们的过程中使用return而没有后果,自我保留等等。

在这里使用Thread的好处是,在某些情况下你不会得到LocalJumpError - 并且返回将发生在最意想不到的地方(在顶级方法上,意外地跳过其余​​部分&#39) ; s身体)。

主要的缺点是潜在的开销(如果在您的方案中足够的话,可以用yield替换Thread + join。)

答案 5 :(得分:0)

我在ruby中为Web框架编写DSL时遇到了同样的问题...(Web框架Anorexic会摇滚!)...

无论如何,我挖到ruby内部并找到了一个简单的解决方案,使用当Proc调用返回时返回的LocalJumpError ...到目前为止它在测试中运行良好,但我不确定它是不是全性能:

def thing(*args, &block)
  if block
    block_response = nil
    begin
      block_response = block.call
    rescue Exception => e
       if e.message == "unexpected return"
          block_response = e.exit_value
       else
          raise e 
       end
    end
    puts "value=#{block_response}"
  else
    puts "no block given"
  end
end

救援段中的if语句可能看起来像这样:

if e.is_a? LocalJumpError

但对我来说这是一个未知的领域,所以我会坚持到目前为止测试的内容。

答案 6 :(得分:0)

我找到了一种方法,但它涉及将方法定义为中间步骤:

def thing(*args, &block)
  define_method(:__thing, &block)
  puts "value=#{__thing}"
end

thing { return 6 * 7 }

答案 7 :(得分:0)

我很欣赏 s12chung 的 answer。这是我对他的回答的一点改进。它可以避免使用方法 __thing 弄乱上下文。

def thing(*args, &block)
  o = Object.new
  o.define_singleton_method(:__thing, block)
  puts "value=#{o.__thing}"
end

thing { return 6 * 7 }