dwalin has asked for the wisdom of the Perl Monks concerning the following question:

Let's say I have a list of @objects interspersed with scalar values, and I want to get a list of unique stringified values of all @objects. Each $object has "" operator overloaded so naturally I try to do the intuitive thing:

my @objects = (); my %unique = map { "$_" => 1 } @objects; print sort keys %unique;

Contrary to my expectation, the construct above produces compiler error:

Not enough arguments for map at test.pl line 2, near "} @objects" syntax error at test.pl line 2, near "} @objects" Execution of test.pl aborted due to compilation errors.

Changing the offending line to...

my %unique = map { ''.$_ => 1 } @objects;

...resolves the error. But still there's something I can't understand about it. Why the more natural-looking construct is erroneous, while less intuitive works as expected? Is that another compiler quirk I just have to live with?

Regards,
Alex.

Replies are listed 'Best First'.
Re: Strange compiler behavior with map?
by BrowserUk (Patriarch) on Sep 05, 2011 at 08:03 UTC

    This: { "$_" => 1 }, looks to the compiler sufficiently like an anonymous hash that it (heuristically) decides it must be one, rather than an in-line block. And if it was an anonymous hash, then it would mean you had omitted the comma between it and the next argument. Hence the error.

    There are various other strategies you can use to cause the heuristic to decide the other way. A not exhaustive list include:

    • my %unique  = map {; "$_" => 1 } @objects;
    • my %unique  = map { +"$_" => 1 } @objects;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Strange compiler behavior with map?
by ikegami (Patriarch) on Sep 05, 2011 at 08:05 UTC

    The compiler has to decide whether "{" is the start of a block or the start of a hash constructor.

    The standard way to disambiguate would be:

    my %unique = map {; "$_" => 1 } @objects;

    But this would work:

    my %unique = map { $_ => 1 } @objects;

    By the way, if you want to preserve the original order, you can use:

    my %seen; my @unique = grep !$seen{$_}++, @objects; print @unique;

    Also, it won't convert your objects into strings.

      The purpose of this statement is to normalize objects and scalar values to their string representation, hence explicit "$_"; I wanted to make it stand out and catch the eye when I will need to return to that code. {; does the trick nicely, thanks.

      And many thanks for the second idiom, now that's a real nice solution. I think I've seen it before but somehow it didn't registered until now. I need to remember it the next time.

      Regards,
      Alex.

        Stringifying version:

        my %seen; my @unique = grep !$seen{$_}++, map "$_", @objects;
        my %seen; my @unique = map !$seen{$_}++ ? "$_" : (), @objects;
Re: Strange compiler behavior with map?
by repellent (Priest) on Sep 05, 2011 at 08:05 UTC
    You need to disambiguate the curlies for the compiler. See the hashem and showem examples at Making References.
      Nice, I like {; natural to type (no need to press shift) and looks like a handlebar mustache
      my @f = 1 .. 3; # syntax error my %f = map { "$_" => 1 ; } @f; my %f = map {; "$_" => 1 } @f; print %f; __END__ 113121

      ;}) meat with salt ({: