http://www.perlmonks.org?node_id=1035186

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

Background: In the recent thread help with simplifying program, BillKSmith recommended the use of <Algorithm::Combinatorics>. This uses iterators to go through combinations of data. I love to write terse code so I ended up with (which I did not post in the original thread):

use strict; use warnings; use Algorithm::Combinatorics qw(combinations); my $rep = 5; # should be 100 my @data = 0..$rep; my $iter = combinations( \@data, 4 ); while( my ( $z, $y, $x, $w ) = @{ $iter->next // last } ) { next unless $w-2*$x+$y or $x-2*$y+$z; print "$w, $x, $y, $z\n"; }

My question is about the // last part. I reads nicely like "next or last" but I am aware that this exit from the while loop makes while redundant. I might as well use goto or write

while(1){ my ( $z, $y, $x, $w ) = @{ $iter->next // last }; ... }

What do you think about this use of last here? Is it a nice short way of exiting the loop when the iterator becomes undef when exhausted or is this breaking too much of the loop's structure? Or are there even hidden dangers that I have not spotted?

Replies are listed 'Best First'.
Re: Unconventional exit from while loop
by BrowserUk (Patriarch) on May 24, 2013 at 18:40 UTC

    Infinity prefer:

    while( my $r = $iter->next ) { my ( $z, $y, $x, $w ) = @{ $r }; next unless $w-2*$x+$y or $x-2*$y+$z; print "$w, $x, $y, $z\n"; }

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Unconventional exit from while loop
by tobyink (Canon) on May 24, 2013 at 19:01 UTC

    Something like this is fairly commonly seen:

    while( my ( $z, $y, $x, $w ) = @{ $iter->next // [] } ) { ...; }

    When next returns undef, you end up assigning an empty array to ( $z, $y, $x, $w ). List assignment evaluates to the size of the array on the right hand side, which is zero, thus false, and exits the loop.

    In place of // you could use || or or.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: Unconventional exit from while loop
by LanX (Saint) on May 24, 2013 at 21:56 UTC
    Generally I'm trying to avoid to call iterators in scalar context, because of edge cases where a useful return value is false and stops the while loop.

    I prefer clear stop values...

    For instance

    while( my ( $z, $y, $x, $w ) = @{ $iter->next // last } ) { }

    is terminated for

    1.  return (); i.e.  return;
    2.  return undef;
    3.  return [];

    but

    while( my ($a_permutation) = $iter->next ) { ... }

    is only terminated in the first case. You still have the liberty to return undef or [] as valid values.

    In your special snippet undef or [] don't make much sense, but this approach helps avoiding quirks like 0 but true !

    Anyway I wonder why the iterator was designed to return an array reference...

    Why not right away returning a list?

    while( my @permutation = $iter->next ) { ... }

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Re: Unconventional exit from while loop
by Laurent_R (Canon) on May 24, 2013 at 22:04 UTC

    Hmmm, I would suppose that, as of today, we are all writing in structured programming, aren't we?

    The old debate on GOTOs is gone and has been away for quite a while, isn't it?

    I have written quite a number of programs packed with GOTOs or equivalent statements in various languages (Fortran, Basic, Assembler, etc.) in the 1980s. Thanks God, N. Wirth and E. Dijstra, among others, have given us better constructs, such as if/then/else, case or switch, etc.

    Having said that, I am using very commonly a proprietary language where the GOTO is the only exit forward. I have a procedure looping on the records of a database, I am making a number of tests on the current record, but my only way to say that this record is not one that I want to process is to say something like "goto next record". I do not have a 'next' or a 'continue' instruction or whatever, so the goto is my favorite.

    Consider the following pseudo code:

    if (a != b) if (a != c) if (a != d) if (a != e) f = a else ... endif endif endif

    This is just a short example, I have seen examples with about ten level of if/then/else nesting. This is becoming difficult to follow. Compare to:

    next if a == b; next if a == c; next if a == d; ...

    Isn't the second version clearer?

    In the proprietary language I am talking about, I have to do something like this:

    if (a == b) goto next_record; if (a == c) goto next_record; ...

    Should I faint on the GOTO word?

    I do not think so. In such a case, I claim that GOTOs make the code much cleaner and much more readable that a strict adhesion to some idealized version of structured programing.

    The authors ot the Camel Book say somewhere (if I remember correctly) that progamming is often a matter of descending a decision tree. There is nothing wrong, in their view, with using the next, continue and last keywords to shortcircuit annoying cases and get right to the interesting stuff.

    Having said all that, it is of course up to you to decide how far you want to go challenging some principles that we have all been nurtured with.

    Well, my two cents worth...

Re: Unconventional exit from while loop
by hdb (Monsignor) on May 26, 2013 at 08:15 UTC

    Thanks all for your insightful replies. In summary I will probably:

    • not use last in this context anymore,
    • mark BrowserUK's version as the most maintainable one, that I really should be using,
    • often use tobyink's version, just to save one line of code,
    • and examine the iterator's interface carefully to make sure nothing unexpected can happen.
    I also share Laurent_R's view that there should not be any taboos related to breaking out of loops as long as it helps to make the code clearer and supports the intention of the coder.

      not use last in this context anymore,

      Ever since I replied above, this has been ticking over in the back of my mind: I wasn't quite sure quite why I so prefer the normal version.

      Especially since I'm not adverse to using last in unusual ways.

      And I think finally the penny has dropped. It's because it effectively renders that while loop condition test redundant, but doesn't stop being tested each time around the loop. So you are effectively testing two conditions every time, only one of which can ever be false.

      Kind of like:

      while( 1 ) { my( $w, $x, $y, $z ) = @{ $iter->next // last }; ... }

      Except that the loop conditional isn't optimised away.


      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        > ... renders that while loop condition test redundant, ... only one of which can ever be false.

        Nope, the while loop condition exits on empty lists and your code doesn't do the same thing.

        Try returning an empty array ref [].

        see also Re: Unconventional exit from while loop

        Cheers Rolf

        ( addicted to the Perl Programming Language)

        This is what has caused me to ask the question in the first place. The condition in while always returns true until the iterator is exhausted, but before while has a chance to exit, last takes over. So the whole while statement seems redundant.

      I checked Algorithm::Combinatorics documentation and source, undef is exit-code and all other return values are array-refs.

      Note [] is listed as valid edge case but would make your loop exit.

      So the version from the synopsis (which is also BUK's) is the generic one for this module and works always...

      while (my $x = $iter->next() ){ ... }

      ...cause array-refs are always true.

      Cheers Rolf

      ( addicted to the Perl Programming Language)