我能否在Ruby的Proc中使用toplevel break来打破循环?

时间:2021-03-23 20:48:49

Questions

  • Why does break within a proc jump out of three loops all the way to puts 8? It's pretty counter-intuitive.
  • 为什么在一个过程中打破从三个循环中跳出来,直到放8?它很反直觉的。
  • Is there a way to make it break out of the innermost loop, that is, to puts 6?
  • 有没有办法让它跳出最里面的循环,也就是放到6?

Code

3.times do
  puts "outer loop"
  break_proc = proc { break }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      loop do
        puts 4
        break_proc.call
        puts 5
      end
      puts 6
    end
    puts 7
  end
  puts 8
end
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8

3 个解决方案

#1


5  

TL;DR

The behavior you're seeing is a result of attempting to treat a Proc object like a snippet of code passed to Kernel#eval, or thinking that a toplevel break inside a Proc is the same as a bare break keyword inside a loop. An explanation for the behavior is provided, but the real solution is to avoid doing what you're doing.

您所看到的行为是尝试处理Proc对象的结果,比如将代码片段传递给内核#eval,或者认为在Proc内的toplevel中断与循环中一个空的break关键字相同。对此行为给出了一个解释,但真正的解决方案是避免做你正在做的事情。

Procs Carry Context

Why does break within a proc jump out of three loops all the way to puts 8?

为什么在一个过程中打破从三个循环中跳出来,直到放8?

This happens because a Proc object contains a Binding to the context in which it's created, and the break keyword is exiting the iterator block and returning to its calling context. Specifically, you're creating the Proc in the top-level loop here:

这是因为Proc对象包含到创建它的上下文的绑定,而break关键字正在退出迭代器块并返回到它的调用上下文。具体地说,您正在这里的*循环中创建Proc:

3.times do
  puts "outer loop"
  break_proc = proc { break }

One could be forgiven for thinking that Ruby's break just exits a loop wherever its called, but its behavior is more complex than that, especially when you're trying to do something odd like a toplevel break inside a Proc. Your use case for break is even covered in The Ruby Programming Language, where it says:

有人情有可原,Ruby的休息就退出循环的地方,但它的行为是比这更复杂的,尤其是当你想做一些奇怪的像一个顶层的休息在Proc。你的用例是覆盖在Ruby编程语言,它说:

[A break] causes the block to return to its iterator and the iterator to return to the method that invoked it. Because procs work like blocks, we expect break to do the same thing in a proc. We can’t easily test this, however. When we create a proc with Proc.new, Proc.new is the iterator that break would return from. And by the time we can invoke the proc object, the iterator has already returned. So it never makes sense to have a top-level break statement in a proc created with Proc.new[.]

[中断]导致块返回到它的迭代器,而迭代器返回到调用它的方法。因为procs就像块一样工作,所以我们期望break在proc中做同样的事情。当我们用proc .new创建一个proc时,proc .new是中断将返回的迭代器。当我们可以调用proc对象时,迭代器已经返回。因此,在使用proc .new[]创建的proc中使用*break语句是没有意义的。

— David Flanagan and Yukihiro Matsumoto. The Ruby Programming Language (Kindle Locations 8185-8192). O'Reilly Media.

- David Flanagan和Yukihiro Matsumoto。Ruby编程语言(Kindle location 8185-8192)。O ' reilly媒体。

When you create deeply nested loops and then complicate that with objects that carry runtime bindings, the results aren't always what you expect. The behavior you're seeing is not a bug, although it may be a misfeature in some cases. You'd have to ask the language designers why it behaves this way if you want a reason for the implementation semantics rather an explanation for the behavior you're seeing.

当您创建深度嵌套的循环,并将其与携带运行时绑定的对象复杂化时,结果并不总是您所期望的那样。您看到的行为不是bug,尽管在某些情况下它可能是一个错误的特性。如果您想要实现语义的原因,而不是您所看到的行为的解释,那么您就必须询问语言设计人员为什么要这样做。

Breaking Loops

Is there a way to make it break out of the innermost loop, that is, to puts 6?

有没有办法让它跳出最里面的循环,也就是放到6?

Yes, but not with break inside a Proc. Replacing the Proc#call with an actual inline break statement does what you expect and is the "simplest thing that could possibly work," but you can also use throw and catch if you want to adjust your nesting level. For example:

是的,但是不要使用Proc内部的break。用实际的内联break语句替换proc#调用可以实现您所期望的,并且是“可能工作的最简单的东西”,但是如果您想调整嵌套级别,也可以使用throw和catch。例如:

3.times do
  puts "outer loop"
  break_proc = proc { throw :up }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      catch :up do
        loop do
          puts 4
          break_proc.call
          puts 5
        end
      end
      puts 6
    end
    puts 7
  end
  puts 8
end

This will yield:

这将产生:

outer loop
1
2
3
4
6
3
4
6
3
4
6

and endlessly loop inside the third loop where you puts 3.

在第三个循环中无限循环。

So, this will do what you're asking, but may or may not do what you want. If it helps, great! If not, you may want to ask a separate question with some real data and behavior if you want to find a more elegant data structure or decompose your task into a set of collaborating objects.

所以,这将做你所要求的,但可能做也可能不做你想做的。如果有帮助,太棒了!如果不是,如果您希望找到更优雅的数据结构或将任务分解为一组协作对象,那么您可能希望使用一些真实的数据和行为来提出一个单独的问题。

#2


3  

Because of context binding break escapes from the loop defined at the same level:

由于上下文绑定中断从同一层定义的循环中逃逸:

3.times do
  puts 1
  loop do
    break_proc = proc {|b| break }
    puts 2
    loop do
      puts 3
      loop do
        puts 4
        break_proc.call
          puts 5
      end
      puts 6
    end
    puts 7
    raise 'break other loops'
  end
  puts 8
end

=>

= >

1
2
3
4
7
1.rb:18:in `block (2 levels) in <main>': break other loops (RuntimeError)

Easiest way to break from your construction - return a boolean from the block indicating if loop should be terminated (... = proc{ true }/break if break_proc.call), or use throw:

最简单的方法从你的构造中断开-返回一个布尔值,指示如果循环终止(…= proc{true}/break if break_proc.call),或使用throw:

3.times do
  puts "outer loop"
  break_proc = proc {|b| throw :breakit }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      catch :breakit do
        loop do
          puts 4
          break_proc.call
          puts 5
        end
      end
      puts 6
      raise 'break the other loops...'
    end
    puts 7
  end
  puts 8
end

#3


0  

If you want to break till 6 block you could do this

如果你想打破到6块,你可以这么做

3.times do
  puts "outer loop"
  break_proc = proc { break }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      loop do
        puts 4
        break
        puts 5
      end
      puts 6
    end
    puts 7
  end
  puts 8
end

#1


5  

TL;DR

The behavior you're seeing is a result of attempting to treat a Proc object like a snippet of code passed to Kernel#eval, or thinking that a toplevel break inside a Proc is the same as a bare break keyword inside a loop. An explanation for the behavior is provided, but the real solution is to avoid doing what you're doing.

您所看到的行为是尝试处理Proc对象的结果,比如将代码片段传递给内核#eval,或者认为在Proc内的toplevel中断与循环中一个空的break关键字相同。对此行为给出了一个解释,但真正的解决方案是避免做你正在做的事情。

Procs Carry Context

Why does break within a proc jump out of three loops all the way to puts 8?

为什么在一个过程中打破从三个循环中跳出来,直到放8?

This happens because a Proc object contains a Binding to the context in which it's created, and the break keyword is exiting the iterator block and returning to its calling context. Specifically, you're creating the Proc in the top-level loop here:

这是因为Proc对象包含到创建它的上下文的绑定,而break关键字正在退出迭代器块并返回到它的调用上下文。具体地说,您正在这里的*循环中创建Proc:

3.times do
  puts "outer loop"
  break_proc = proc { break }

One could be forgiven for thinking that Ruby's break just exits a loop wherever its called, but its behavior is more complex than that, especially when you're trying to do something odd like a toplevel break inside a Proc. Your use case for break is even covered in The Ruby Programming Language, where it says:

有人情有可原,Ruby的休息就退出循环的地方,但它的行为是比这更复杂的,尤其是当你想做一些奇怪的像一个顶层的休息在Proc。你的用例是覆盖在Ruby编程语言,它说:

[A break] causes the block to return to its iterator and the iterator to return to the method that invoked it. Because procs work like blocks, we expect break to do the same thing in a proc. We can’t easily test this, however. When we create a proc with Proc.new, Proc.new is the iterator that break would return from. And by the time we can invoke the proc object, the iterator has already returned. So it never makes sense to have a top-level break statement in a proc created with Proc.new[.]

[中断]导致块返回到它的迭代器,而迭代器返回到调用它的方法。因为procs就像块一样工作,所以我们期望break在proc中做同样的事情。当我们用proc .new创建一个proc时,proc .new是中断将返回的迭代器。当我们可以调用proc对象时,迭代器已经返回。因此,在使用proc .new[]创建的proc中使用*break语句是没有意义的。

— David Flanagan and Yukihiro Matsumoto. The Ruby Programming Language (Kindle Locations 8185-8192). O'Reilly Media.

- David Flanagan和Yukihiro Matsumoto。Ruby编程语言(Kindle location 8185-8192)。O ' reilly媒体。

When you create deeply nested loops and then complicate that with objects that carry runtime bindings, the results aren't always what you expect. The behavior you're seeing is not a bug, although it may be a misfeature in some cases. You'd have to ask the language designers why it behaves this way if you want a reason for the implementation semantics rather an explanation for the behavior you're seeing.

当您创建深度嵌套的循环,并将其与携带运行时绑定的对象复杂化时,结果并不总是您所期望的那样。您看到的行为不是bug,尽管在某些情况下它可能是一个错误的特性。如果您想要实现语义的原因,而不是您所看到的行为的解释,那么您就必须询问语言设计人员为什么要这样做。

Breaking Loops

Is there a way to make it break out of the innermost loop, that is, to puts 6?

有没有办法让它跳出最里面的循环,也就是放到6?

Yes, but not with break inside a Proc. Replacing the Proc#call with an actual inline break statement does what you expect and is the "simplest thing that could possibly work," but you can also use throw and catch if you want to adjust your nesting level. For example:

是的,但是不要使用Proc内部的break。用实际的内联break语句替换proc#调用可以实现您所期望的,并且是“可能工作的最简单的东西”,但是如果您想调整嵌套级别,也可以使用throw和catch。例如:

3.times do
  puts "outer loop"
  break_proc = proc { throw :up }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      catch :up do
        loop do
          puts 4
          break_proc.call
          puts 5
        end
      end
      puts 6
    end
    puts 7
  end
  puts 8
end

This will yield:

这将产生:

outer loop
1
2
3
4
6
3
4
6
3
4
6

and endlessly loop inside the third loop where you puts 3.

在第三个循环中无限循环。

So, this will do what you're asking, but may or may not do what you want. If it helps, great! If not, you may want to ask a separate question with some real data and behavior if you want to find a more elegant data structure or decompose your task into a set of collaborating objects.

所以,这将做你所要求的,但可能做也可能不做你想做的。如果有帮助,太棒了!如果不是,如果您希望找到更优雅的数据结构或将任务分解为一组协作对象,那么您可能希望使用一些真实的数据和行为来提出一个单独的问题。

#2


3  

Because of context binding break escapes from the loop defined at the same level:

由于上下文绑定中断从同一层定义的循环中逃逸:

3.times do
  puts 1
  loop do
    break_proc = proc {|b| break }
    puts 2
    loop do
      puts 3
      loop do
        puts 4
        break_proc.call
          puts 5
      end
      puts 6
    end
    puts 7
    raise 'break other loops'
  end
  puts 8
end

=>

= >

1
2
3
4
7
1.rb:18:in `block (2 levels) in <main>': break other loops (RuntimeError)

Easiest way to break from your construction - return a boolean from the block indicating if loop should be terminated (... = proc{ true }/break if break_proc.call), or use throw:

最简单的方法从你的构造中断开-返回一个布尔值,指示如果循环终止(…= proc{true}/break if break_proc.call),或使用throw:

3.times do
  puts "outer loop"
  break_proc = proc {|b| throw :breakit }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      catch :breakit do
        loop do
          puts 4
          break_proc.call
          puts 5
        end
      end
      puts 6
      raise 'break the other loops...'
    end
    puts 7
  end
  puts 8
end

#3


0  

If you want to break till 6 block you could do this

如果你想打破到6块,你可以这么做

3.times do
  puts "outer loop"
  break_proc = proc { break }

  puts 1
  loop do
    puts 2
    loop do
      puts 3
      loop do
        puts 4
        break
        puts 5
      end
      puts 6
    end
    puts 7
  end
  puts 8
end