Ending an iteration of a loop from a subroutine
Asked Answered
F

5

8

I have a program with a while loop which has several points where certain conditions require some action to be taken and then the remainder of the iteration to be skipped.

As this is always going to be the same code, I wanted to put it in a subroutine, but when I try using 'next;' as the final statement in the sub I get a warning (Exiting subroutine via next at ...), although it appears to work as intended.

i.e. without the sub:

while (#condition) {

    ## do stuff

    if (#condition to skip iteration) {
        ## action
        next;
    }

    ## do more stuff, with the above block repeated several times
}

with sub:

while (#condition) {

    ## do stuff

    &skip if (#condition to skip iteration);

    ## do more stuff, with more calls to &skip
}

sub skip() {
    ## action
    next;
}

The rest of the code in the block/sub is so short that putting all but the next; statement in a sub pretty much defeats the object of using a subroutine.

My question is:

  • Is using 'next;' in a subroutine in this way ok, or is it dangerous/poor practice?
  • Is there a better way to skip the rest of the iteration from a subroutine?
  • If it is ok and there isn't a better way, is there a way to suppress the warning?
Fettling answered 12/9, 2014 at 12:16 Comment(0)
B
10

It's easy to suppress the warning. Just place the following line above your next:

no warnings "exiting";

The warning is there for a reason - using next in a helper sub like that can be confusing for the next person who has to read the code (who might be you in 6 months' time!) because the next does not occur lexically within the loop block. If you're just reading the loop block, you might not notice that next further down the file; and if you're just reading the definition of the subroutine, you might not be sure what that next is for. You need to read both parts of the code together to make sense of it. That makes it more confusing than using next directly inside the loop.

Also, it limits the re-usabilty of the skip() sub you've just defined. Want to re-use that skip() sub inside another loop? You'd better hope that the skip logic still makes sense in the new loop.

If you've considered all that and still want to go ahead, then just disable the warning as I showed above. Warnings are not errors, they're just warnings. That's why warnings are called "warnings". They're designed to bring your attention to something potentially problematic; not to stop you from doing something you've decided is useful.

Barbee answered 12/9, 2014 at 12:59 Comment(0)
S
6

From the Learning perl 6th edition page 179 (footnote)

It’s probably not a good idea, but you could use these loop-control operators from inside a subroutine to control a loop that is outside the subroutine. That is, if a subroutine is called in a loop block, and the subroutine executes last when there’s no loop block running inside the subroutine, the flow of control will jump to just after the loop block in the main code. This ability to use loop control from within a subroutine may go away in a future version of Perl, and no one is likely to miss it.

The solution could be what others already said, or you can do additional test in the sub, like

use strict;
use warnings;

while(<>) {
    chomp;
    maybeskip($_) && next if m/2/; #maybe skip if match 2
    print "$_\n";
}

sub maybeskip {
    $_[0] !~ m/0/; #skip only if not match 0
    # the sub retuns the result of the last executed expression
    # if this is not wanted you should use explicit return $value;
}

for the

seq 25 | perl script

prints:

1
3
4
5
6
7
8
9
10
11
13
14
15
16
17
18
19
20

e.g. skipped all matched 2 but not 20

Sweeting answered 12/9, 2014 at 12:43 Comment(0)
C
4

Your warning instinct is probably right. As you say, next just is not the kind of statement one embeds in a subroutine. Even though you do not prefer it, this is almost certainly better:

while (#condition) {

    ## do stuff

    &skip, next if (#condition to skip iteration);

    ## do more stuff, with more calls to &skip
}

sub skip() {
    ## action
}

(I happen to have a fairly relaxed attitude toward nonstandard programming techniques, especially if they are not overused. If you feel strongly about embedding the next in this instance, then I'll not argue with you, since it's your program. However, nevertheless, since you have asked advice, I would agree with your instinct that embedding the next is probably not a good idea.)

Columbic answered 12/9, 2014 at 12:23 Comment(1)
Why using round brackets at sub skip? No need to use prototyping or signatures here. Maybe less confusing to remove them.Night
I
3

Warnings would not warn if this is good practice. Use your function so that it returns early instead of next. You can check for function result and decide whether to skip to next iteration, or skip unconditionally to next,

use warnings;

sub foo {
  print ($_, "\n");
}

for (1..10) {
  foo(), next if $_ %2;;
}
Intestate answered 12/9, 2014 at 12:23 Comment(0)
Z
3

You can put part/all of the skip condition inside the subroutine by returning truthy to next and falsey to keep going using the and operator.

while (#condition) {

    ## do stuff

    &skip and next if (#condition to skip iteration);

    ## or like this
    next if (#condition to skip iteration) and &skip;

    ## or like this
    (#condition to skip iteration) and &skip and next;

    ## do more stuff, with more calls to &skip
}

sub skip() {
    ## action
    return $common_skip_condition;
}
Zicarelli answered 12/9, 2014 at 16:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.