在Ruby块中使用“返回”


87

我正在尝试将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,但结果是相同的。


为什么要避免隐式返回值?
marcgg

@marcgg -我有一个相关的问题在这里- stackoverflow.com/questions/25953519/...
sid Smith,2014年

Answers:


171

只需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被调用的行

4
中断proc将引发异常
gfreezy 2012年

您能否从“下一个从块返回值并结束调用”中引用该信息的位置?我想了解更多。
user566245

如果我没记错的话,那本书来自《 Ruby编程语言》一书(我现在手边没有)。我刚刚检查了google,我相信它来自于那本书:librairie.immateriel.fr/fr/read_book/9780596516178/…和2个下一页(不是我的内容和页面,我只是用谷歌搜索了)。但是我真的推荐原著,里面有更多关于宝石的解释。
MBO

我也从脑海中回答,只检查了irb中的内容,这就是为什么我的回答不专业或不完整的原因。有关更多信息,请查阅《 Ruby编程语言》一书。
MBO

我希望这个答案是最重要的。我不能足够投票。
btx9000'9

20

您无法在Ruby中做到这一点。

return关键字总是从在当前上下文中的方法或λ返回。在块中,它将从定义闭包的方法中返回。无法使其从调用方法或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
# ...

有来自块/ proc中返回一个详细的文章在这里
ComDubh


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

1

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

无论如何,我研究了ruby的内部结构,并找到了一个简单的解决方案,使用了Proc调用return时返回的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

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


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
}

此黑客可让用户在自己的procs中使用return,而不会造成任何后果,自我保留等。

在这里使用Thread的好处是,在某些情况下,您将不会收到LocalJumpError-并且返回将发生在最意外的位置(在顶级方法旁边,意外地跳过了其余部分)。

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


1

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

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

thing { return 6 * 7 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.