smsiebe has asked for the wisdom of the Perl Monks concerning the following question:

I ran into this while debugging a fellow programmers code. Took a bit to realize the problem and was wondering if this is an expected situation...

When calling a subroutine/function from within a loop, if we call 'next' within that function (outside of a looping construct itself), it actually iterates the loop of the calling scope rather than issuing an error. Code example:

use strict; my @stuff = (1, 2, 3, 4, 5); foreach my $s (@stuff) { test($s); print "past\n"; } sub test { my ( $s ) = @_; if ($s == 3) { next; #note that this is not within a loop in the #scope of the subroutine } else { print $s . "\n"; } }

What I would expect is that I get a compile time error similar to if I attempted to call next outside a loop. Instead what I get is:

1 past 2 past 4 past 5 past

This shows that the subroutine is actually iterating the last loop, independent of the scope.

My opinion is on the fence on whether I like this result or not...but I do find it unexpected. I assumed that the actions within a subroutine is in its own scope and would not be able to affect the outer loop of a calling scope.

The functionality makes it seem as if the batch of the sub is 'imported' into the process, retaining the scope of the batch for variables but not for things like looping constructs.

Thoughts?

Thanks,

steve

Replies are listed 'Best First'.
Re: Loop controls transcends scope?
by merlyn (Sage) on Jan 15, 2009 at 17:43 UTC
    It's not violating scope. last/next/redo/goto respect dynamic scope (determined by runtime call stack) not lexical scope. Think of it as similar to the difference between local() and my().
Re: Loop controls transcends scope?
by Corion (Patriarch) on Jan 15, 2009 at 17:44 UTC

    If you ask Perl to tell you about weird things by using the warnings pragma, or launching Perl with the -w command line switch, it will tell you:

    >perl -we "sub nxt { next }; for (1..3) { nxt if /2/}" Exiting subroutine via next at -e line 1.
Re: Loop controls transcends scope?
by JavaFan (Canon) on Jan 15, 2009 at 17:43 UTC
    Yes, it works this way. Test::More exploits this feature. It's unlikely to be changed in perl5; too much code relies on this (mis)feature.
Re: Loop controls transcends scope?
by TGI (Parson) on Jan 15, 2009 at 21:39 UTC

    If you decide Corion's excellent advice and enable warnings for your code while body of the code intact, you can suppress this warning easily.

    sub test { my ( $s ) = @_; if ($s == 3) { no warnings 'exiting'; # suppress the warning for this lexical +scope only. next; } else { print $s . "\n"; } }

    My personal preference is to use something more like this:

    use strict; use warnings; my @stuff = 1..5; for my $s (@stuff) { next if is_bad_stuff($s); my $formatted = format_stuff($s); print "$formatted: passed\n"; } sub is_bad_stuff { my ( $s ) = @_; return $s == 3; } sub format_stuff { my ( $s ) = @_; $s = '<UNDEF>' return "Formatted $s"; }

    The downside of this is the extra function call. The upside is that it's (hopefully) easier to read and maintain.

    I like to collect code with side-effects (like printing) into as few places as possible, so my format_stuff routine preps the data for output but does not emit it. I know this is a toy example, but the habit has served me well.


    TGI says moo

Re: Loop controls transcends scope?
by smsiebe (Novice) on Jan 15, 2009 at 17:49 UTC
    All great answers, thanks for the quick and informative replies.