This is a read-only archive!

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.

December 03, 2006 @ 4:13 AM PST
Cateogory: Programming
Tags: Perl, Ruby

3 Comments

Dave
Quoth Dave on December 03, 2006 @ 5:32 AM PST

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

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

Brian
Quoth Brian on December 03, 2006 @ 6:01 AM PST

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. :)

Slaven Rezi?
Quoth Slaven Rezi? on October 04, 2007 @ 10:59 AM PDT

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 $@;