http://qs321.pair.com?node_id=11119158

As you might know, I have been using Perl::Critic on my code over the last several days. After I finished checking my code on the gentle setting, I kicked it up a notch and used stern. Well, Perl::Critic set on stern gave me screens full of problems, and the biggest one is I use the expression form of map and grep. I looked around the web to find out why.

Here is my opinion that could be very wrong.

All of the examples I found of the expression form of map and grep would lead to inevitable problems, and I can see why the writers of those examples would jump on using the block form for both. However, the one thing all of the examples had in common is the disuse of parentheses. I feel that if the expression form of a map or grep is used with parentheses, it is contained within them.

The map below would lead to problems, since there is nothing containing the expression or on which list(s) the map is being applied.

my @colors = qw(red yellow green cyan blue magenta); my @grey_scale = qw(white grey black); my @list = map "$_ beads", @colors, @grey_scale;

However, I do not think this needs a block form to contain the map and list if parentheses are used.

my @list = map( "$_ beads", @colors ), @grey_scale;

Now the map is contained within parentheses, and @grey_scale does not get beads mapped to it. However, if one must use the block form, parentheses would still be needed to contain the mapped items.

my @list = ( map { "$_ beads" } @colors ), @grey_scale;

I think the expression form with parentheses is easier on the eyes, but it is just my opinion. I can understand using the block form if the map were more complex, however, I think I would write a separate subroutine instead of loading the block with more than two or three modifications and use the expression form.

sub make_beads { my $color = shift; if ($color =~ /red|green|blue/) { $color .= ' sparkley'; } else { $color .= ' shiny'; } $color .= ' beads'; return $color; } my @list = map( make_beads($_), @colors), @grey_scale;

Converting from expression to block form is also problematic as it is not always as simple as the above would suggest, especially with grep. I have also found the expression form of grep to be easier to use, in one case (I can not remember the specifics) I could not get the block form of grep to work.

If I use sort with map and/or grep on the same list, I will wrestle the block forms until I get the results I want, because in that case, it is easier on my eyes than trying to mix expression forms with the block of sort.

my @list = ( map { make_beads($_} } sort { $a cmp $b } grep { <something> } @colors ), @grey_scale;

I know I can ignore Perl::Critic's results on this and other issues, however, I do want code that is more acceptable by the community. So, I am trying to decide if I want to start wrestling with Perl on this or not. (I ran Perl::Critic on all of my modules, and it found 330 lines where I used the expression forms of map and grep, so this is fairly big to me.)

I hope I am not too wrong about this.

My OS is Debian 10 (Buster); my perl versions are 5.28.1 local and 5.16.3 or 5.30.0 on web host depending on the shebang.

No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: Expression form of map or grep
by haukex (Bishop) on Jul 10, 2020 at 20:00 UTC

    Most of the default Perl::Critic policies come from TheDamian's Perl Best Practices. The rationale for the policies can be found in perlcritic's verbose output, the POD (in this case, Perl::Critic::Policy::BuiltinFunctions::RequireBlockMap), and of course the book (page 169).

    $ perlcritic 11119158.pl Expression form of "map" at line 8, column 12. See page 169 of PBP. +(Severity: 4) $ perlcritic --verbose 11 11119158.pl Expression form of "map" at line 8, near 'my @list = map "$_ beads", @ +colors, @grey_scale;'. BuiltinFunctions::RequireBlockMap (Severity: 4) The expression forms of `grep' and `map' are awkward and hard to r +ead. Use the block forms instead. @matches = grep /pattern/, @list; #not ok @matches = grep { /pattern/ } @list; #ok @mapped = map transform($_), @list; #not ok @mapped = map { transform($_) } @list; #ok

    Although Perl::Critic policies usually have a very good reason, some of them can be seen as stylistic choices and one can disagree with PBP if one knows what one is doing. The severify of policies can be changed via the config file, e.g.:

    $ cat ~/.perlcriticrc severity = 3 [BuiltinFunctions::RequireBlockMap] severity = 2
Re: Expression form of map or grep
by tobyink (Canon) on Jul 10, 2020 at 21:11 UTC

    Generally speaking, the block form is more flexible and more readable, however the expression form of grep is pretty readable if you're just doing a regexp match:

    my @results = grep /searchterm/i, @inputs;

    When things get more complicated than that, I'd generally prefer the block form.

    The expression form can run slightly faster if optimization is a concern.

Re: Expression form of map or grep
by ForgotPasswordAgain (Priest) on Jul 12, 2020 at 00:03 UTC

    FWIW, I agree with everything you said. However, I think you made a little error:

    my @list = map( "$_ beads", @colors ), @grey_scale;

    I think @list will not contain anything from @grey_scale, and would warn with something like "Useless use of private array in void context", because the assignment precedes the comma operator.

    I do prefer the expression form with parentheses, though, partly because of an (I believe) obsolete reason which was that the block form would generate a new lexical space whereas the expression form wouldn't, so the expression form was apparently slightly more performant (on very large lists).

    I also like making hashes with something like...

    my %h = map +($_ => 1), @a;

    ...I guess because it seems the most generally correct, given the possible parsing bugs with Perl and map, but at the same time I can see that it's possibly confusing, especially given the "array in void context" comment above. :)

      I think @list will not contain anything from @grey_scale, and would warn with something like "Useless use of private array in void context" ...

      Ah, good catch!

      c:\@Work\Perl\monks\Lady_Aleena>perl -wMstrict -MData::Dump -le "my @colors = qw(a b c); my @grey_scale = qw(x y); my @list = map( qq{$_ beads}, @colors ), @grey_scale; dd \@list; " Useless use of private array in void context at -e line 1. ["a beads", "b beads", "c beads"]
      Another set of parentheses is needed to include the second (and any subsequent) array:
      c:\@Work\Perl\monks\Lady_Aleena>perl -wMstrict -MData::Dump -le "my @colors = qw(a b c); my @grey_scale = qw(x y); my @list = (map( qq{$_ beads}, @colors ), @grey_scale); dd \@list; " ["a beads", "b beads", "c beads", "x", "y"]


      Give a man a fish:  <%-{-{-{-<

      It was my intent for @grey_scale to be part of @list, but not have "beads" mapped to it. I have a few modules that are lists starting with or have a map in the middle of the list, here is an example:

      My OS is Debian 10 (Buster); my perl versions are 5.28.1 local and 5.16.3 or 5.30.0 on web host depending on the shebang.

      No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
      Lady Aleena

        In the code as shown, "map" just melds in the array reference construction. Perhaps that is what you prefer.

        I personally would either make a temporary variable to hold the map result; or rearrange the code to highlight map function. On the second note ...

        'troll' => [ 'troll', map( "$_ troll", qw(desert freshwater giant ice saltwater snow spectr +al), 'two-headed' ), 'trobold', @$tralg, @$throglin ]
Re: Expression form of map or grep
by jcb (Vicar) on Jul 12, 2020 at 03:37 UTC

    As tobyink mentions, the expression forms can be slightly faster because they avoid entering/leaving a block, but the block forms offer just that: a block where additional lexicals can be defined. In more complex uses, the block form can therefore be needed.

    The block forms are also easier to read when using map to expand an array into key-value pairs to load a hash — fat comma will not parse correctly without extra parentheses and I get confused about whether those parentheses will define the entire argument list or just the leading expression.

    my %index = map { $_->key => $_ } @objects;

    is easier for me to read than:

    my %index = map (($_->key => $_), @objects);
Re: Expression form of map or grep ( .perlcriticrc )
by beech (Parson) on Jul 13, 2020 at 21:08 UTC

      I haven't written it.

      My OS is Debian 10 (Buster); my perl versions are 5.28.1 local and 5.16.3 or 5.30.0 on web host depending on the shebang.

      No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
      Lady Aleena
Re: Expression form of map or grep
by karlgoethebier (Abbot) on Jul 13, 2020 at 07:38 UTC

    BTW, this node might be also interesting for you. Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: Expression form of map or grep
by karlgoethebier (Abbot) on Jul 12, 2020 at 16:23 UTC

    See also

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help