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.
精彩评论