Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl: Why should "last" not be used to exit a grep or map?

Tags:

perl

The perldocs say the following about last:

last cannot be used to exit a block that returns a value such as eval {}, sub {}, or do {}, and should not be used to exit a grep() or map() operation.

Why should it be avoided in a grep() or map()? I'm especially curious about map since it is an alternative to the foreach construct. The docs seem to insist on not doing something without describing the consequences.

like image 807
vol7ron Avatar asked Mar 20 '12 23:03

vol7ron


3 Answers

First, realise that grep and map are transparent to next, last and redo, just like if is.

$ perl -e'
   print "A";
   for (8,9) {
      print "B";
      print map { print "C"; next; print "D"; $_ } 1,2,3;
      print "E";
   }
   print "F\n";
'
ABCBCF

But the docs doesn't say last cannot be used to exit map, it says it shouldn't used. It's not clear what that means.

  • Don't use them thinking they will affect map and grep?
  • Don't use them that way because your reader might think they affect map and grep?
  • Don't use them that way because it can leave Perl in a wonky state?
  • Don't use them that way because the behaviour might change in the future?

I don't know.

I don't think it's #3 because I don't know of any bad side-effects of using next, last and redo to leave a map or grep callback.

The funny thing, leaving map and grep using a loop construct doesn't even warn even though leaving a sub in the same fashion does.

$ perl -wE'while (1) { map { last; } 1; }'

$ perl -wE'while (1) { sub { last; }->(); }'
Exiting subroutine via last at -e line 1.
like image 170
ikegami Avatar answered Nov 19 '22 16:11

ikegami


map is meant for mapping one list to another. It certainly isn't an alternative to foreach. I have seen too often things like

map { print "$_\n" } @data;

which commits the sin of using map for its side-effects and throwing away the resulting list. You wouldn't consider using

my $x = 99;
sqrt(my $y = $x * $x);
print $y;

and you would get a warning. For the same reason you shoudn't use map when you mean for.

For a practical reason, consider

print "A";
print map { print "B"; last; print "C";} 1,2,3;
print "Z";

OUTPUT

AB

so in this case the last exits the entire program. In general, using last or next inside a map or grep block exits the containing block, if there is one, otherwise the entire program. This is something to be avoided, hence "last ... should not be used to exit a grep() or map() operation".

like image 36
Borodin Avatar answered Nov 19 '22 17:11

Borodin


In Perl, there is a decent amount of functionality overlap between for/foreach, grep, and map. However, just because you can potentially use them all to solve the same problem, doesn't mean that they're all the best tool for that particular job (see: using a screwdriver to pound a nail).

  • grep: Its purpose is to filter a list and return the resulting subset.
  • map: Its purpose is to modify a list and return the resulting modified list.
  • for/foreach: Its purpose is to iterate through a list and do something.

Now, you can accomplish every use of grep and map with for/foreach. grep and map are basically syntactic sugar for specific uses of list processing (for/foreach) that are particularly common and useful. By design, by convention, and by best practices, they trade certain things for increased convenience and clarity.

One of those things, is that grep and map are intended to return a value. Using them in void context is discouraged, and considered to violate idiomatic Perl. If you aren't returning a value, use for/foreach. If you are returning a value, then last interrupts that process.

If you are following best practices for Perl, your use of grep and map will always be returning a value, which means that using last should be avoided.

like image 23
Christopher Cashell Avatar answered Nov 19 '22 18:11

Christopher Cashell