我正在尝试将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,但结果是一样的。
答案 0 :(得分:161)
在此上下文中只需使用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
的行答案 1 :(得分:17)
你不能在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
# ...
答案 2 :(得分:3)
你是从错误的角度来看待它。
这是thing
的问题,而不是lambda。
def thing(*args, &block)
block.call.tap do |value|
puts "value=#{value}"
end
end
thing {
6 * 7
}
答案 3 :(得分: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
答案 4 :(得分: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
}
这个hack允许用户在他们的过程中使用return而没有后果,自我保留等等。
在这里使用Thread的好处是,在某些情况下你不会得到LocalJumpError - 并且返回将发生在最意想不到的地方(在顶级方法上,意外地跳过其余部分&#39) ; s身体)。
主要的缺点是潜在的开销(如果在您的方案中足够的话,可以用yield
替换Thread + join。)
答案 5 :(得分:0)
我在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
但对我来说这是一个未知的领域,所以我会坚持到目前为止测试的内容。
答案 6 :(得分:0)
我找到了一种方法,但它涉及将方法定义为中间步骤:
def thing(*args, &block)
define_method(:__thing, &block)
puts "value=#{__thing}"
end
thing { return 6 * 7 }
答案 7 :(得分:0)
我很欣赏 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 }