开发者

Why am I getting unexpected results from chained calls to map?

I'm using Getopt::Lucid to process CLO and I've run into an interesting and unexpected problem. The following code:

push @clo_spec, map { Switch($_) } qw(-c -m -s -p),
                map { Switch($_) } qw(--help --man --usage --version),
                map { Switch($_) } qw(--debug --verbose),
   开发者_运维技巧             map { Param($_)  } keys %$rc_spec_ref
;

my $clo_o = Getopt::Lucid->getopt(\@clo_spec);

generates the following error:

'Getopt::Lucid::Spec=HASH(0x9383847)' is not a valid option name/alias

Now, Getopt::Lucid is configured by quoting an string expression representing valid options and then passing those strings to one of six subroutines which return blessed hashes. Each subroutine represents a type of option; switch, counter, parameter, list or key-pair.

The interesting part is that if any three map expressions are removed,

push @clo_spec, #map { Switch($_) } qw(-c -m -s -p),
                map { Switch($_) } qw(--help --man --usage --version),
                #map { Switch($_) } qw(--debug --verbose),
                #map { Param($_)  } keys %$rc_spec_ref
;

then everything works fine. The even more interesting part is that if you encase each map expression in parentheses, everything also works fine:

push @clo_spec, (map { Switch($_) } qw(-c -m -s -p)),
                (map { Switch($_) } qw(--help --man --usage --version)),
                (map { Switch($_) } qw(--debug --verbose)),
                (map { Param($_)  } keys %$rc_spec_ref)
;

The above leads me to believe that this problem isn't related to a bug in Getopt::Lucid. Also, I considered the above fix after looking at the map function's reference which mentions that sometimes map can get confused with commas. Perl will flatten embedded lists, and the surrounding parentheses seem to have the effect of delineating each map expression, but I really don't understand what's going on.

Can someone please explain?


The map function takes a list as an argument and generates a list as a result. You can chain map statements together (feeding the output of one map as the input to another) which is what your first example does. Adding parentheses around the individual map operators breaks the chain.

When reading chained map (or grep) statements, read from right to left.

push @clo_spec,
    map { Switch($_) } qw(-c -m -s -p),
    map { Switch($_) } qw(--help --man --usage --version),
    map { Switch($_) } qw(--debug --verbose),
    map { Param($_)  } keys %$rc_spec_ref;

The last map calls Param() for each key from %$rc_spec_ref and returns the results. The map above that calls Switch() for the values --debug, --verbose, and each result from the last map. The map blocks above those get even longer argument lists with the flags in qw() having the results of the other map blocks concatenated with them.

Adding parentheses around each map block changes the way the code parses, causing each map to be treated individually rather than being daisy-chained.


$ perl -MO=Deparse
push @clo_spec, map { Switch($_) } qw(-c -m -s -p),
                map { Switch($_) } qw(--help --man --usage --version),
                map { Switch($_) } qw(--debug --verbose),
                map { Param($_)  } keys %$rc_spec_ref
;

my $clo_o = Getopt::Lucid->getopt(\@clo_spec);
^D
push @clo_spec, map({Switch($_);} ('-c', '-m', '-s', '-p'), map({Switch($_);} (
'--help', '--man', '--usage', '--version'), map({Switch($_);} ('--debug',
'--verbose'), map({Param($_);} keys %$rc_spec_ref))));
my $clo_o = 'Getopt::Lucid'->getopt(\@clo_spec);
- syntax OK

If you're ever confused by how Perl is parsing something, B::Deparse is terrific.

In this case, you can clearly see that each map is operating not just on the qw() you've given it, but also the results of the map following it.


What is I think is happening is that without the parens, the output one each map is fed as arguments to the map preceding it. Say you only had two of these maps:

push @clo_spec, (map { Switch($_) } qw(-c -m -s -p)),
                (map { Switch($_) } qw(--help --man --usage --version)),

The last one executes first, feeding Switch '--help', '--man', etc. Switch returns the Hashes you expect. Then, the first map executes, feeding it's switch the '-c', '-m', 's' and '-p'. But then it also feeds it the result of the first switch, which explains why you're getting errors that HASH(...) isn't a valid option name.

The solution? Either use parens to make the arguments to each map explicit[1], or use multiple push lines, one for each map.

[1] If you do use parens, I'd recommend instead of (map ....) writing map(....), as it would be clearer why the parens are there.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜