关于lambda:在Ruby块中使用’return’

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,但结果是相同的。


在这种情况下,只需使用next

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的行。


你不能用红宝石做这个。

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

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
# ...


你的观点不对。这是thing的问题,而不是lambda。

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,并且返回将发生在最意想不到的地方(在顶级方法的一侧,意外地跳过它的主体的其余部分)。

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


在哪里调用了这个东西?你在教室里吗?

您可以考虑使用类似的方法:

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

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