Search code examples
rubyloopscycle

How to break outer cycle in Ruby?


In Perl, there is an ability to break an outer cycle like this:

AAA: for my $stuff (@otherstuff) {
         for my $foo (@bar) {
             last AAA if (somethingbad());
         }
      }

(syntax may be wrong), which uses a loop label to break the outer loop from inside the inner loop. Is there anything similar in Ruby?


Solution

  • What you want is non-local control-flow, which Ruby has several options for doing:

    • Continuations,
    • Exceptions, and
    • throw/catch

    Continuations

    Pros:

    • Continuations are the standard mechanism for non-local control-flow. In fact, you can build any non-local control-flow (subroutines, procedures, functions, methods, coroutines, state machines, generators, conditions, exceptions) on top of them: they are pretty much the nicer twin of GOTO.

    Cons:

    • Continuations are not a mandatory part of the Ruby Language Specification, which means that some implementations (XRuby, JRuby, Ruby.NET, IronRuby) don't implement them. So, you can't rely on them.

    Exceptions

    Pros:

    • There is a paper that proves mathematically that Exceptions can be more powerful than Continuations. IOW: they can do everything that continuations can do, and more, so you can use them as a replacement for continuations.
    • Exceptions are universally available.

    Cons:

    • They are called "exceptions" which makes people think that they are "only for exceptional circumstances". This means three things: somebody reading your code might not understand it, the implementation might not be optimized for it (and, yes, exceptions are godawful slow in almost any Ruby implementation) and worst of all, you will get sick of all those people constantly, mindlessly babbling "exceptions are only for exceptional circumstances", as soon as they glance at your code. (Of course, they won't even try to understand what you are doing.)

    throw/catch

    This is (roughly) what it would look like:

    catch :aaa do
      stuff.each do |otherstuff|
        foo.each do |bar|
          throw :aaa if somethingbad
        end
      end
    end
    

    Pros:

    • The same as exceptions.
    • In Ruby 1.9, using exceptions for control-flow is actually part of the language specification! Loops, enumerators, iterators and such all use a StopIteration exception for termination.

    Cons:

    • The Ruby community hates them even more than using exceptions for control-flow.