在Ruby块中使用“返回”

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

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

如果我在上面的例子中使用'return',我得到一个LocalJumpError。 我知道这是因为有问题的块是Proc而不是lambda。 代码的作品,如果我删除“返回”,但我真的希望能够在这种情况下使用“返回”。 这可能吗? 我已经尝试将块转换为lambda,但结果是相同的。

在此上下文中使用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中testing这个代码片段,你没有方法,这就是为什么你有LocalJumpError
  • break返回块的值并结束它的调用。 如果你的块是由yield或者.call调用的,那么也可以从这个迭代器中断
  • next返回块的值并结束它的调用。 如果你的块是通过yield或者.call调用的,那么next返回值就是调用yield那一行

你不能在Ruby中这样做。

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

Rubyspeccertificate了这确实是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 # ... 

你从错误的angular度看待它。 这是thing的问题,而不是拉姆达。

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

事情在哪里被调用? 你在课堂上吗?

你可以考虑使用这样的东西:

 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 

我相信这是正确的答案,尽pipe有缺点:

 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 } 

这个黑客允许用户在他们的过程中使用返回而没有任何后果,自我保存等等。

这里使用Thread的好处是,在某些情况下,你不会得到LocalJumpError – 并且返回将发生在最意想不到的地方(在顶层方法旁边,意外地跳过它的其余部分)。

主要的缺点是潜在的开销(如果在你的场景中足够的话,你可以把Thread + join换成只有yield的值)。

我有同样的问题在Ruby中写一个Web框架的DSL …(Web框架Anorexic将摇滚!)…

无论如何,我深入ruby内部,发现一个简单的解决scheme,使用返回的Proc调用时返回的LocalJumpError …它在目前为止的testing中运行良好,但我不确定它是否是完整的:

 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 

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

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

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