Re: map problem
by moritz (Cardinal) on Mar 09, 2012 at 10:57 UTC
|
This is another instance of the problem that a pair of curlies can stand for an anonymous subroutine or a hash reference. You can disambiguate with a semicolon in the block:
my %y = map {; "prefix_$_" => 1 } qw(a b c);
It's more of a design limitation than a bug in perl or in you :-)
| [reply] [d/l] |
|
Ok, but why is it neccessary only in the second case?
| [reply] |
|
The decision to parse an opening curly bracket as a block or a hash ref is really just a heuristic. When the parser sees a string quote, it decides in favor of the hash ref, whereas a variable makes it assume a block.
Or put differently, the parser cheats as hell, and sometimes that backfires. But not always.
| [reply] |
Re: map problem
by GrandFather (Saint) on Mar 09, 2012 at 11:13 UTC
|
Not a bug (really) and not stupid, just a subtle foible of Perl that bites occasionally. From the perlfunc documentation for map:
{ starts both hash references and blocks, so map { ... could be either the start of map BLOCK LIST or map EXPR, LIST. Because perl doesn't look ahead for the closing } it has to take a guess at which its dealing with based what it finds just after the {. Usually it gets it right, but if it doesn't it won't realize something is wrong until it gets to the } and encounters the missing (or unexpected) comma. The syntax error will be reported close to the } but you'll need to change something near the { such as using a unary + to give perl some help:
- %hash = map { "\L$_", 1 } @array # perl guesses EXPR. wrong
- %hash = map { +"\L$_", 1 } @array # perl guesses BLOCK. right
- %hash = map { ("\L$_", 1) } @array # this also works
- %hash = map { lc($_), 1 } @array # as does this.
- %hash = map +( lc($_), 1 ), @array # this is EXPR and works!
True laziness is hard work
| [reply] |
|
I also found that adding a string cat operation seemed to clarify things enough for the parser also. I'm not sure which of the various solutions is "the best" if there even is such a thing!
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my %x = map {; "prefix_$_" => 1 } qw(a b c);
my %y = map { "prefix_"."$_" => 1 } qw(a b c);
my %z = map { ''."prefix_$_" => 1 } qw(a b c);
print Dumper \%y, \%x, \%z;
| [reply] [d/l] |
|
my %y = map { "prefix_".$_ => 1 } qw(a b c);
There's no need to put $_ inside quotes, only to have Perl do variable interpolation on that string. :)
Alex / talexb / Toronto
"Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds
| [reply] [d/l] [select] |
|
|
I like the variant with the semicolon best, because it's the most explicit way to say that something should be parsed as a block. Which conveys a clear message to the maintenance programmer: I wanted a block here, and you'd better leave the semicolon there.
Relying on the exact semantics of the disambiguation heuristics doesn't leave any traces in the code that there was a problem, and the unsuspecting maintenance programmer will run into the same problems as you did.
| [reply] |
|
Re: map problem
by JavaFan (Canon) on Mar 09, 2012 at 10:58 UTC
|
That's because when Perl sees the { following the map, it has to guess whether it's seeing the beginning of a block, or the beginning of a hashref, and it may only keep one token ahead. Which is a string, so map guesses hashref, so, it assumes it's going to parse map EXPR, LIST. And then there's no comma.
Use:
my %y = map {;"prefix_$_" => 1 } qw(a b c);
The semi-colon tells Perl it's a block. | [reply] [d/l] [select] |
|
In my particular case I would prefer this syntax:
my %y = map { ("x$_" => 1) } qw(a b c);
or would you say that using the semicolon is already an accepted idiom for such cases? | [reply] [d/l] |
|
my @y = map { ("x$_" => 1) }, qw(a b c);
But feel free to do whatever works for you. Don't do something just because others do it, or not do something because others don't. | [reply] [d/l] [select] |
|
my %y = map( ( "x$_" => 1 ), qw( a b c ) );
| [reply] [d/l] |
|
The code with the semi-colon
- has an unambiguous effect (not a valid hash constructor),
- has an unambiguous purpose (coder obviously doing something to appease the compiler), and
- is idiomatic.
{ ("x$_" => 1) } is still a valid hash constructor, and it's not clear that the parens are required, so it's not as clear as it could be.
| [reply] [d/l] |
Re: map problem
by ForgotPasswordAgain (Priest) on Mar 09, 2012 at 22:51 UTC
|
Everyone already answered the problem.
But another thing is I avoid using curly braces except when mapping to hashrefs. In this case:
my %y = map +( "prefix_$_" => 1 ), qw/a b c/;
As far as I know, using a block unnecessarily creates a lexical scope, which seems like a waste.
And speaking of waste, I notice you're pointing your hash to values of 1, probably intending to look at keys(%y) later. That's an unfortunate idiom if you ask me, as it points to a different copy of "1" every time. It's often better (though not important here for 3 values) to alias to undef if you're only concerned about getting a unique list of keys. | [reply] [d/l] |