sub uniq { my %seen; grep !$seen{$_}++, @_ } : No longer allowed. Fix?

241 Views Asked by At

Please read first the UPDATE below !!!!

As can be seen elsewhere on stackoverflow, the following perl sub has been popular for some years for extracting the unique elements of a list

sub uniq { my %seen; grep !$seen{$_}++, @_ }

However, since some recent perl version, the above code produces the error

syntax error at line 2, near "sub uniq "
Can't use global $_ in "my" at test5.pl line 2, near "{$_"
syntax error at test5.pl line 2, near "++,"

and @_ is similarly disallowed. There is various advice to be found that one should use "local $_" and some such, but I cannot get it to work. Also it seems that maybe sub uniq needs to become a function that returns the unique list?

In short: What does the canonical one-liner/function/sub uniq definition look like now? (perl 5.34)

UPDATE: user hobbs had the right idea. It was a typo/syntax error, namely a missing semicolon. A simple example (contrived but makes the point) to generate the error message is to feed the following two lines into perl

1
sub uniq { my %seen; grep !$seen{$_}++, @_ } 

And to make the error message go away, just add the semicolon

1;
sub uniq { my %seen; grep !$seen{$_}++, @_ }

The error message seemed very "realistic" to me, so I overlooked the possibility of a simple syntax error. Hopefully I can be forgiven :). If nothing else this post can serve as en example that a seemingly very relevant error message should not always be taken literally.

2

There are 2 best solutions below

0
hobbs On BEST ANSWER

I think you just made a typo somewhere. There's nothing wrong with that function, and I've tested it to work both on 5.34.0 and blead using the code copied out of your question. I would probably use List::Util::uniq (or uniqnum or uniqstr in special cases) or List::UtilsBy::uniq_by, but nothing has changed in Perl to break that snippet.

5
ikegami On

First of all, I wouldn't call that snippet the canonical way to remove duplicates; I'd say that's List::Util's uniq.

That said, there's no problem with the code you posted. Specifically, it doesn't produce the error you claim it does. The code is perfectly valid in all version of Perl since at least 5.6, including 5.34 and 5.36.

$ 5.34t/bin/perl -wE'
   sub uniq { my %seen; grep !$seen{$_}++, @_ }
   say for uniq qw( a b a b c );
'
a
b
c

The quoted error message results from passing a punctuation variable (e.g. $!, $1, etc) to my.

$ perl -e'my $!;'
Can't use global $! in "my" at -e line 1, near "my $!"
Execution of -e aborted due to compilation errors.

A program that once worked wouldn't normally start giving this error as a result of an upgrade, since Perl has been giving this error since lexical variables and my have been introduced in Perl 5.6.[1]

There's one exception: my $_.

5.10 introduced the three related features smart-matching, given and lexical $_ (my $_).

They have many problems. So many that the concept of experimental features was introduced in 5.18, and these were downgraded to experimental features.

The experiments were deemed failures. While smart-matching and given have been hard to remove (since so many people are using the experimental features), that's not the case for lexical $_. 5.24 saw its removal. Attempts to use my $_ in 5.24+ results in the error you posted once again.

$ 5.8/bin/perl -e'my $_;'
Can't use global $_ in "my" at -e line 1, near "my $_"
Execution of -e aborted due to compilation errors.

$ 5.10t/bin/perl -e'my $_;'

$ 5.16t/bin/perl -e'my $_;'

$ 5.18t/bin/perl -e'my $_;'
Use of my $_ is experimental at -e line 1.

$ 5.22t/bin/perl -e'my $_;'
Use of my $_ is experimental at -e line 1.

$ 5.24t/bin/perl -e'my $_;'
Can't use global $_ in "my" at -e line 1, near "my $_"
Execution of -e aborted due to compilation errors.

The code you posted doesn't pass a punctuation variable to my. As such, it doesn't produce the error you claim it does.

If it did use my with a punctuation variable, the error would be resolved by replacing the my $X with local $X, local *X or for $X ().


  1. I'm guessing it's been giving the error since 5.6. I can only verify back to 5.8.