在 Ruby 块中使用“返回”

Posted

技术标签:

【中文标题】在 Ruby 块中使用“返回”【英文标题】:Using 'return' in a Ruby block 【发布时间】:2011-01-20 11:51:50 【问题描述】:

我正在尝试将 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 - 我有一个相关的问题 - ***.com/questions/25953519/…。 【参考方案1】:

在这种情况下只需使用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 中测试这个 sn-p 你没有方法,这就是为什么你有 LocalJumpError break 从块返回值并结束其调用。如果你的块被yield.call 调用,那么break 也会从这个迭代器中中断 next 从块返回值并结束其调用。如果您的块被yield.call 调用,则next 将值返回到调用yield 的行

【讨论】:

进程中断会引发异常 您能否引用从“下一个从块返回值并结束它的调用”中获取此信息的位置。我想了解更多。 如果我没记错的话,它来自The Ruby Programming Language 书(我现在手头没有)。我刚刚检查了谷歌,我相信它来自那本书:librairie.immateriel.fr/fr/read_book/9780596516178/… 和从那里的 2 个下一页(这不是我的内容和我的页面,我只是用谷歌搜索了它)。但我真的推荐原著,它有更多的宝石解释。 我也从脑海中回答,只检查 irb 中的内容,这就是为什么我的回答不是技术性的或完整的。有关更多信息,请查看《Ruby 编程语言》一书。 我希望这个答案在顶部。我不能足够支持它。【参考方案2】:

你不能在 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
# ...

【讨论】:

有一篇详细的从block/proc返回的文章here【参考方案3】:

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

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

thing 
  6 * 7

【讨论】:

【参考方案4】:

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

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

thing  return 6 * 7 

【讨论】:

【参考方案5】:

我很佩服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 

【讨论】:

【参考方案6】:

在哪里调用事物?你在上课吗?

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

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

【讨论】:

【参考方案7】:

我在用 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

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

if e.is_a? LocalJumpError

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

【讨论】:

【参考方案8】:

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

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 允许用户在其 procs 中使用 return 而不会产生任何后果,self 被保留等。

在这里使用 Thread 的优点是在某些情况下您不会得到 LocalJumpError -并且返回将发生在最意想不到的地方(在***方法上,意外地跳过了它的主体的其余部分)。

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

【讨论】:

以上是关于在 Ruby 块中使用“返回”的主要内容,如果未能解决你的问题,请参考以下文章

在 .map 代码块中引用 .map 返回对象(数组)?

如何在 Ruby 的 MULTI 块中读取 Redis?

有人可以解释 Ruby 在块中使用管道字符吗?

在 Ruby gsub 块中使用命名的捕获组(正则表达式)

Ruby:如何在代码块中使用5个主要的Find方法

当用作回调时,Ruby 块中的“return”和“break”是不是无用?