我正在尝试将 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,但结果是一样的。
只需在此上下文中使用 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 调用,那么也从这个迭代器中中断中断
next 从块返回值并结束其调用。如果您的块被 yield 或 .call 调用,则 next 将值返回到调用 yield 的行
你不能在 Ruby 中做到这一点。
return
关键字 always 从当前上下文中的方法或 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
# ...
你从错误的角度来看它。这是 thing
的问题,而不是 lambda。
def thing(*args, &block)
block.call.tap do |value|
puts "value=#{value}"
end
end
thing {
6 * 7
}
我很佩服 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 }
我在用 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
救援段中的 if 语句可能看起来像这样:
if e.is_a? LocalJumpError
但这对我来说是未知的领域,所以我会坚持我到目前为止测试的内容。
我找到了一种方法,但它涉及将方法定义为中间步骤:
def thing(*args, &block)
define_method(:__thing, &block)
puts "value=#{__thing}"
end
thing { return 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
我相信这是正确的答案,尽管有缺点:
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 -并且返回将发生在最意想不到的地方(在顶级方法上,意外地跳过了它的其余部分)。
主要缺点是潜在的开销(如果在您的场景中足够的话,您可以只用 yield
替换 Thread+join)。