Using 'return' in a Ruby block
我尝试使用Ruby1.9.1作为嵌入式脚本语言,这样"最终用户"代码就可以用Ruby块编写。这方面的一个问题是,我希望用户能够在块中使用"return"关键字,因此他们不需要担心隐式返回值。考虑到这一点,我希望能够做到:
1 2 3 4 5 6 7 8 | def thing(*args, &block) value = block.call puts"value=#{value}" end thing { return 6 * 7 } |
如果在上面的示例中使用"return",则会得到一个localJumpError。我知道这是因为所讨论的块是proc而不是lambda。如果我删除"return",代码就可以工作,但是我更希望在这个场景中能够使用"return"。这有可能吗?我尝试将块转换为lambda,但结果是相同的。
在这种情况下,只需使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $ 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 的行。
你不能用红宝石做这个。
Rubyspec证明了这确实是Ruby的正确行为(不可否认,这不是真正的实现,但目标是与C Ruby完全兼容):
1 2 3 4 5 6 7 8 | 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 # ... |
你的观点不对。这是
1 2 3 4 5 6 7 8 9 | def thing(*args, &block) block.call.tap do |value| puts"value=#{value}" end end thing { 6 * 7 } |
号
我相信这是正确的答案,尽管有缺点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 } |
。
这个黑客程序允许用户在程序中使用返回,而不会造成任何后果,自我保护等等。
在这里使用线程的好处是,在某些情况下,您不会得到localJumpError,并且返回将发生在最意想不到的地方(在顶级方法的一侧,意外地跳过它的主体的其余部分)。
主要的缺点是潜在的开销(如果在您的场景中足够多的话,您可以用
在哪里调用了这个东西?你在教室里吗?
您可以考虑使用类似的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
我找到了一种方法,但它涉及到将方法定义为中间步骤:
1 2 3 4 5 6 | def thing(*args, &block) define_method(:__thing, &block) puts"value=#{__thing}" end thing { return 6 * 7 } |
我在用Ruby编写Web框架的DSL时也遇到过同样的问题…(网络框架厌食症会很厉害!)…
总之,我深入研究了Ruby的内部结构,发现了一个简单的解决方案,它使用当一个proc调用返回时返回的localJumpError…到目前为止,它在测试中运行良好,但我不确定它是否是完整的证据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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语句可能如下所示:
1 | if e.is_a? LocalJumpError |
。
但对我来说这是一个未知的领域,所以我会坚持到目前为止我所测试的。