从原始范围之外的红宝石块返回

时间:2009-05-27 16:53:10

标签: ruby

我想做一些看起来像这样的事情:

def make_wrapped_block(&block)
  puts "take_block:before"
  func = lambda do
    puts "Before calling make_wrapped_block's passed block"
    block.call
    puts "After calling make_wrapped_block's passed block"
  end
  puts "take block:after"
  func
end

def make_block
  make_wrapped_block do
    puts "Before return"
    return :pi
    puts "After return"
  end
end

make_block.call

..那里会有很多make_block个方法,它们会生成make_wrapped_block提供类似初始化和清理的闭包。

因为传递给make_wrapped_block中的make_block的块返回,这会导致LocalJumpError:

[ cpm juno ~/tmp/local-jump ] ruby bad.rb
take_block:before
take block:after
Before calling make_wrapped_block's passed block
Before return
bad.rb:15:in `make_block': unexpected return (LocalJumpError)
        from bad.rb:5:in `call'
        from bad.rb:5:in `make_wrapped_block'
        from bad.rb:20:in `call'
        from bad.rb:20

现在,我可以使用稍微不同的语法来理解这个想法:

def make_wrapped_block(block)
  puts "take_block:before"
  func = lambda do
    puts "Before calling make_wrapped_block's passed block"
    block.call
    puts "After calling make_wrapped_block's passed block"
  end
  puts "take block:after"
  func
end

def make_block
  make_wrapped_block(lambda {
    puts "Before return"
    return :pi
    puts "After return"
  })
end

make_block.call

这是有效的,因为当您从使用lambda创建的匿名函数返回时,它将退出匿名函数,而使用Proc.new和匿名块时,它会尝试从其定义的范围返回。不能传递它们并安全返回。

是否有一种安全的方法可以从创建范围之外的传递块返回?
第二种方法效果很好,但语法比第一种方式有点丑。

2 个答案:

答案 0 :(得分:2)

好吧,你可以赶上LocalJumpError

def make_wrapped_block(&block)
  puts "take_block:before"
  func = lambda do
    puts "Before calling make_wrapped_block's passed block"
    begin
      block.call
    rescue LocalJumpError
      # allow return / break
    end
    puts "After calling make_wrapped_block's passed block"
  end
  puts "take block:after"
  func
end

#...

make_block.call

但这有点难看。

如果你不介意让回报有点尴尬,你可以改用throw

def make_wrapped_block(&block)
  puts "take_block:before"
  func = lambda do
    puts "Before calling make_wrapped_block's passed block"
    catch (:return) { block.call }
    puts "After calling make_wrapped_block's passed block"
  end
  puts "take block:after"
  func
end

def make_block
  make_wrapped_block do
    puts "Before throw"
    throw :return
    puts "After throw"
  end
end

但是如果对于那些写你make_block的人来说这可能有点奇怪 方法,除非是你。

答案 1 :(得分:2)

Ruby方法/块/闭包/ lambdas是非常不一致的并且是一个痛苦的屁股。以下是idiosyncrasies的全面指南。

您的示例中的内容是returndo...end闭包不是真正的闭包(出于性能原因)。因此,return不会退出do...end,而是返回周围的方法。但是,在lambda中,return退出lambda,而不是封闭方法,这是预期的行为。