【问题标题】:How to break out from a ruby block?如何从红宝石块中脱颖而出?
【发布时间】:2010-11-27 00:41:09
【问题描述】:

这里是Bar#do_things

class Bar   
  def do_things
    Foo.some_method(x) do |x|
      y = x.do_something
      return y_is_bad if y.bad? # how do i tell it to stop and return do_things? 
      y.do_something_else
    end
    keep_doing_more_things
  end
end

这里是Foo#some_method

class Foo
  def self.some_method(targets, &block)
    targets.each do |target|
      begin
        r = yield(target)
      rescue 
        failed << target
      end
    end
  end
end

我考虑过使用 raise,但我试图使其通用,所以我不想在 Foo 中添加任何具体内容。

【问题讨论】:

    标签: ruby


    【解决方案1】:

    使用关键字next。如果不想继续下一项,请使用break

    当在块中使用next 时,它会导致块立即退出,将控制权返回给迭代器方法,然后迭代器方法可以通过再次调用块开始新的迭代:

    f.each do |line|              # Iterate over the lines in file f
      next if line[0,1] == "#"    # If this line is a comment, go to the next
      puts eval(line)
    end
    

    在块中使用时,break 将控制权转移到块之外,从调用块的迭代器中转移到调用迭代器之后的第一个表达式:

    f.each do |line|             # Iterate over the lines in file f
      break if line == "quit\n"  # If this break statement is executed...
      puts eval(line)
    end
    puts "Good bye"              # ...then control is transferred here
    

    最后,return 在块中的用法:

    return 总是导致封闭方法返回,无论它在块中嵌套的深度如何(lambda 除外):

    def find(array, target)
      array.each_with_index do |element,index|
        return index if (element == target)  # return from find
      end
      nil  # If we didn't find the element, return nil
    end
    

    【讨论】:

    • 谢谢,但 next 只会移动到数组中的下一项。可以退出吗?
    • 您必须使用返回值调用 next。 "def f; x = yield; puts x; end" "f do next 3; puts "o"; end" 这会在控制台上打印 3(但没有 "o")。
    • nextbreakreturn,你无法比较
    • 我在@MarcelJackwerth 添加的关于使用nextbreak 的评论中添加了一个答案。
    • 还有一个叫做redo的关键字,它基本上只是将执行移回当前迭代中的块顶部。
    【解决方案2】:

    使用关键字break 而不是return

    【讨论】:

      【解决方案3】:

      也许您可以使用内置方法在数组中查找特定项目,而不是 each-ing targets 并手动完成所有操作。几个例子:

      class Array
        def first_frog
          detect {|i| i =~ /frog/ }
        end
      
        def last_frog
          select {|i| i =~ /frog/ }.last
        end
      end
      
      p ["dog", "cat", "godzilla", "dogfrog", "woot", "catfrog"].first_frog
      # => "dogfrog"
      p ["hats", "coats"].first_frog
      # => nil
      p ["houses", "frogcars", "bottles", "superfrogs"].last_frog
      # => "superfrogs"
      

      一个例子是这样的:

      class Bar
        def do_things
          Foo.some_method(x) do |i|
            # only valid `targets` here, yay.
          end
        end
      end
      
      class Foo
        def self.failed
          @failed ||= []
        end
      
        def self.some_method(targets, &block)
          targets.reject {|t| t.do_something.bad? }.each(&block)
        end
      end
      

      【讨论】:

      • 不要在 Array 类中添加这样的任意方法!这是非常糟糕的做法。
      • Rails 能做到,为什么他不能呢?
      • @wberry 这并不是说它一定应该。 ;) 不过,一般来说,最好避免猴子修补核心类,除非你有充分的理由(即添加一些非常有用的 generalizable 功能,许多其他代码会发现这些功能很有用)。即便如此,也要小心行事,因为一旦一个类被大量猴子补丁,库很容易开始相互遍历并导致一些极其奇怪的行为。
      【解决方案4】:

      我只想能够跳出一个块 - 有点像前向 goto,与循环无关。事实上,我想在不终止循环的情况下中断循环中的块。为此,我将块设置为一次迭代循环:

      for b in 1..2 do
          puts b
          begin
              puts 'want this to run'
              break
              puts 'but not this'
          end while false
          puts 'also want this to run'
      end
      

      希望这对基于主题行的下一个谷歌用户有所帮助。

      【讨论】:

      • 这是回答问题的唯一回复。值得加分。谢谢。
      • 这对于 break 和 next 都一样。如果将 false 更改为 true,则 next 将停留在外观中,break 将爆发。
      【解决方案5】:

      如果您希望您的块返回有用的值(例如,在使用 #map#inject 等时),nextbreak 也接受一个参数。

      考虑以下几点:

      def contrived_example(numbers)
        numbers.inject(0) do |count, x|
          if x % 3 == 0
            count + 2
          elsif x.odd?
            count + 1
          else 
            count
          end
        end
      end
      

      使用next的等价物:

      def contrived_example(numbers)
        numbers.inject(0) do |count, x|
          next count if x.even?
          next (count + 2) if x % 3 == 0
          count + 1
        end
      end
      

      当然,您总是可以将所需的逻辑提取到方法中并从您的块中调用它:

      def contrived_example(numbers)
        numbers.inject(0) { |count, x| count + extracted_logic(x) }
      end
      
      def extracted_logic(x)
        return 0 if x.even?
        return 2 if x % 3 == 0
        1
      end
      

      【讨论】:

      • 感谢 break 参数的提示!
      • 能否请您更新一个使用break 的具体示例(可能只需将您的next 之一替换为break ..
      • 一件非常有趣的事情。 break something 有效,break(something) 有效,但 true &amp;&amp; break(somehting) 产生语法错误。仅供参考。如果需要条件,则需要使用ifunless
      【解决方案6】:

      nextbreak 在这个简化的例子中似乎做了正确的事情!

      class Bar
        def self.do_things
            Foo.some_method(1..10) do |x|
                  next if x == 2
                  break if x == 9
                  print "#{x} "
            end
        end
      end
      
      class Foo
          def self.some_method(targets, &block)
            targets.each do |target|
              begin
                r = yield(target)
              rescue  => x
                puts "rescue #{x}"
              end
           end
         end
      end
      
      Bar.do_things
      

      输出:1 3 4 5 6 7 8

      【讨论】:

      • break 立即结束 - next 继续下一次迭代。
      【解决方案7】:

      要从 ruby​​ 块中突围,只需使用 return 关键字 return if value.nil?

      【讨论】:

      • return不退出函数吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多