如何从Ruby块中跳出?

461

这里是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中放置任何特定内容。

8个回答

801

使用关键字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

2
谢谢,但是next只会移动到数组的下一项。有没有可能退出? - user169930
5
好的,我会尽力为您翻译。以下是您需要的翻译:
  • next:下一个
  • break:中断
  • return:返回
  • you can't compare:你不能比较
- finiteloop
9
还有一个关键字叫做“redo”,它基本上只是将执行返回到当前迭代中块的顶部。 - Ajedi32
一个小问题:在像这样的文件块中使用 break 还可以吗:File.foreach(filename) { break if condition }?这会让文件保持打开状态吗? - Redoman
1
嗨,如果执行块的方法没有外部迭代器,那么“next,break”会有什么行为?它们是相同的吗?returning control to the iterator method - Sam YC
显示剩余2条评论

70

我想要能够跳出一个代码块 - 类似于前向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
希望这能帮助下一个以此主题为基础而着陆到这里的谷歌搜索者。

5
这是唯一回答了问题的回应,值得得到更多分数。谢谢。 - Alex Nye
这对于break和next都是一样的。如果false被改为true,那么next将会停留在循环中,而break则会跳出循环。 - G. Allen Morris III

42

如果你希望你的块返回一个有用的值(例如在使用#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

1
谢谢你提供使用 break 的参数的提示! - rkallensee
2
你能否提供一个具体的示例,使用break来更新(可能只需要将其中一个next替换为break)。 - Mike Graf
有一件非常有趣的事情。break something 可以正常工作,break(something) 也可以正常工作,但是 true && break(somehting) 会产生语法错误。仅供参考。如果需要条件,则需要使用 ifunless - akostadinov

23

在IT技术中,使用关键词break来代替return


8
也许你可以使用数组中内置的方法来查找特定项,而不是手动遍历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

2
不要像那样向数组类添加任意方法!那是非常糟糕的做法。 - mrbrdo
3
Rails能做到,他为什么不能呢? - wberry
2
@wberry 这并不意味着它一定要这样做。;) 总的来说,最好避免猴子补丁核心类,除非你有一个相当好的理由(即添加一些非常有用的可推广功能,许多其他代码将发现有用)。即使如此,也要小心行事,因为一旦一个类被大量猴子补丁,库就很容易开始互相干扰,导致一些极其奇怪的行为。 - blm768

2
你有四种方法以“非异常”的方式解开堆栈: next, break, returnthrownext将导致该块返回。 break将导致调用块的方法返回。 return将导致定义块的方法返回。 throw将遍历堆栈,直到找到具有匹配符号的catch,并导致其返回。这很像非异常情况下的“轻量级”异常。
它们都可以采取返回值,这将由它们导致的返回值返回,而不是它们通常返回的最后一个表达式的值。
以下是一些示例:
def doSomething
  puts "> doSomething"
  yield
  puts "< doSomething"
end

def withNext
  puts "> withNext"
  doSomething do
    puts "> block"
    puts "* NEXT! causes the block to return to doSomething"
    next
    puts "< block"
  end
  puts "< withNext"
end

def withBreak
  puts "> withBreak"
  doSomething do
    puts "> block"
    puts "* BREAK! causes doSomething to return to withBreak"
    break
    puts "< block"
  end
  puts "< withBreak"
end

def withReturn
  puts "> withReturn"
  doSomething do
    puts "> block"
    puts "* RETURN! causes withReturn to return"
    return
    puts "< block"
  end
  puts "< withReturn"
end

def withThrow
  puts "> withThrow"
  catch :label do
    puts "> catch :label"
    doSomething do
      puts "> block 1"
      doSomething do
        puts "> block 2"
        puts "* THROW! causes catch :label to return to withThrow"
        throw :label
        puts "< block 2"
      end
      puts "< block 1"
    end
    puts "< catch :label"
  end
  puts "< withThrow"
end

withNext
puts "* Done"
puts
withBreak
puts "* Done"
puts
withReturn
puts "* Done"
puts
withThrow
puts "* Done"

输出:

> withNext
> doSomething
> block
* NEXT! causes the block to return to doSomething
< doSomething
< withNext
* Done

> withBreak
> doSomething
> block
* BREAK! causes doSomething to return to withBreak
< withBreak
* Done

> withReturn
> doSomething
> block
* RETURN! causes withReturn to return
* Done

> withThrow
> catch :label
> doSomething
> block 1
> doSomething
> block 2
* THROW! causes catch :label to return to withThrow
< withThrow
* Done

2

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


2
break语句立即结束循环,而next语句则跳过当前迭代,继续执行下一次迭代。 - Ben Aubin

-4

要从 Ruby 块中跳出,只需使用 next 关键字,而不是 return 关键字 return if value.nil?

  • next 终止其所在的 lambda、块或 proc。
  • break 终止调用块或 proc 或 lambda 的方法。

致谢:Ruby block return, break, next


4
return语句不是退出函数吗? - interestedparty333
1
在 Ruby 中,不应该使用 return 来退出一个块! - Almokhtar
我改正了。 - Kiry Meas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接