代码之家  ›  专栏  ›  技术社区  ›  chris_sutter

从块返回-从块内调用的方法返回?

  •  1
  • chris_sutter  · 技术社区  · 14 年前

    我试图定义一个DSL,其中Ruby中的一个块中指定了规则(为了这个例子,规则定义了什么是“好”还是“坏”)。以下是我想要做的事情的(非常简单的)版本:

    def test_block
      # Lots of other code
      is_good = yield   # ... should give me true or false
      # Lots of other code
    end
    
    test_block do
      good if some_condition
      good if some_other_condition
      bad
    end
    

    我能定义方法吗 good bad 那会让街区被打破吗?在上面的示例中,我想:

    • 检查是否 某种情况 是真的,如果是真的,就从这个块中跳出来,让它返回真的。
    • 检查是否 一些其他情况 是真的,如果是真的,就从这个块中跳出来,让它返回真的。
    • 如果仍在块中,则无条件地从块中返回false

    也就是说,我想让上面的代码表现得好像我写了块一样:

    result = test_block do
      break true if some_condition
      break true if some_other_condition
      break false
    end
    

    break 在好/坏方法的定义中,显然不起作用。有没有其他方法来达到我想要的结果,或者我应该考虑一些完全不同的方法来实现这个目标?

    1 回复  |  直到 14 年前
        1
  •  3
  •   Phrogz    14 年前

    您可以在块中引发异常并捕获该异常。

    module Tester
      class Breaker < Exception; end
      class GoodBreak < Breaker; end
      class BaadBreak < Breaker; end
    end
    
    def test_block(name)
      begin
        yield
      rescue Tester::Breaker=>e
        case e
          when Tester::GoodBreak then puts "All is well with #{name}"
          when Tester::BaadBreak then puts "BAD STUFF WITH #{name}"
          else raise
        end
      end
    end
    
    def good; raise Tester::GoodBreak; end
    def bad;  raise Tester::BaadBreak; end
    
    test_block('early out') do
      good if true
      good if puts("NEVER SEE THIS") || true
      bad
    end
    
    test_block('simple pass') do
      good if false
      good if puts("SEE THIS FROM PASS TEST") || true
      bad
    end
    
    test_block('final fail') do
      good if false
      good if puts("SEE THIS BUT PUTS IS NIL")
      bad
    end
    
    #=> All is well with early out
    #=> SEE THIS FROM PASS TEST
    #=> All is well with simple pass
    #=> SEE THIS BUT PUTS IS NIL
    #=> BAD STUFF WITH final fail
    

    下面是另一个使用 throw/catch (谢谢@jledev!)而不是 raise/rescue (更新后传递返回值):

    def test_block(name)
      result = catch(:good){ catch(:bad){ yield } }
      puts "Testing #{name} yielded '#{result}'", ""
    end
    
    def good; throw :good, :good; end
    def bad;  throw :bad,  :bad;  end
    
    test_block('early out') do
      good if true
      good if puts("NEVER SEE THIS") || true
      bad
    end
    
    test_block('simple pass') do
      good if false
      good if puts("SEE THIS FROM PASS TEST") || true
      bad
    end
    
    test_block('final fail') do
      good if false
      good if puts("SEE THIS BUT PUTS IS NIL")
      bad
    end
    
    #=> Testing early out yielded 'good'
    #=> 
    #=> SEE THIS FROM PASS TEST
    #=> Testing simple pass yielded 'good'
    #=> 
    #=> SEE THIS BUT PUTS IS NIL
    #=> Testing final fail yielded 'bad'