Perl vs. Ruby breaking from nested loops

Breaking out of nested blocks is apparently done much differently in Perl and Ruby. In Perl if you run this:

my $counter = 0;
foreach my $outer (1..5) {
    foreach my $inner ('A'..'E') {
        print "$outer: $inner\n";
        last if ++$counter > 4
    }
}

The last is going to break out of the inner-most foreach loop by default. If you want to break out of the OUTER foreach loop you can use line labels:

my $counter = 0;
OUTER: foreach my $outer (1..5) {
    foreach my $inner ('A'..'E') {
        print "$outer: $inner\n";
        last OUTER if ++$counter > 4
    }
}

In Ruby to do the same thing, you have to use a throw/catch block. This is a completely separate mechanism from Ruby's begin/rescue Exception-handling mechanism. throw/catch rather appears to be simply a flow-control mechanism. You raise Exceptions, but you throw Strings or Symbols. The equivalent of the above Perl in Ruby:

counter = 0
catch :OUTER do
    1.upto(5) do |outer|
        'A'.upto'E' do |inner|
            puts "#{outer}: #{inner}"
            throw :OUTER if (counter += 1) > 4
        end
    end
end

EDIT: Originally I thought that Perl's last LABEL; would not find labels except in the same static scope. It must've been a bug in my test code however, because this does work. My mistake! So this does work in Perl:

sub test {
    HERE: foreach my $really_outer (1..5) {
        test2();
    }
}

sub test2 {
    my $counter = 0;
    foreach my $outer (1..5) {
        foreach my $inner ('A'..'E') {
            print "$outer: $inner\n";
            last HERE;
        }
    }
}

test

You can do this in Ruby also. The other nice thing about Ruby is you can return a value from the throw:

def test
    result = catch :HERE do 
        1.upto(5) do |really_outer|
            test2
        end
    end

    puts "RESULT: #{result}"
end

def test2
    counter = 0
    1.upto(5) do |outer|
        'A'.upto'E' do |inner|
            puts "#{outer}: #{inner}"
            throw :HERE, "YOINK" if (counter += 1) > 4
        end
    end
end

test

In Perl you also have the option of using Error.pm, but it looks horrendously hackish to me, as Perl code often does.

The only thing I can think of that's bad about Ruby is that it has two things which are really similar, one for Exceptions and one not. People coming from a different language may think that throw/catch is the Exception-handling mechanism when it's not. But Ruby still wins here.

3 Comments

http://gravatar.com/avatar/1f1a33c73880441ee8d1e191f93acf87.jpg?d=identicon
Dave says:

> # How do you break to HERE?????

WTF??? How about "last HERE;"? RTFM.

Dec 03, 2006 01:32 PM PST
http://gravatar.com/avatar/4d84ec3981443dfd9c287e845b60d2ce.jpg?d=identicon
Brian says:

I was wrong, it does work in Perl. Thanks for pointing it out. You are incredibly rude, however. Please learn how to interact with other human beings in a polite manner. :)

Dec 03, 2006 02:01 PM PST
http://gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e.jpg?d=identicon
Slaven Rezi? says:

Why you need Error.pm? You can do both with stock perl5, the quite ugly exception-style and using a return value from the throw:

my $counter = 0;
eval {
    foreach my $outer (1..5) {
    foreach my $inner ('A'..'E') {
        print "$outer: $inner\n";
        die { message => "jump out!" } if ++$counter > 4
    }
    }
};
use Data::Dumper; print Dumper $@;
Oct 04, 2007 05:59 PM PDT

Speak Your Mind

Preview

Commenting Help

Email / Avatar

  • Supply your email address and your Gravatar will be used.
  • I will never email you and your address won't be published.

No HTML allowed!

All HTML is auto-escaped. Use Markdown. Examples:

  • *emphasis* = emphasis
  • **strong** = strong
  • [link](http://foo.bar) = <a href="http://foo.bar">link</a>
  • `code in backticks` = code in backticks
  •     code indented 4 spaces =
    code indented four spaces
  • > Angle-brace quoted text =
    Angle-brace quoted text