Why does shift think it's receiving a parameter in scalar context?

Viewed 211

I'm trying to determine the maximum number of digits of any number in a list. I've found that I can easily chain functions. As such the following code works as expected:

use strict;
use diagnostics;

my @nums = qw/ 1 23 5432 45 /;
my @lengths = reverse sort map {1 + log($_)/log(10)} @nums;
my $max_length = shift @lengths;
printf "%d\n", $max_length;

Which produces the expected result of 4.

However, when I try to do the shift on the same line as the rest of the list operations:

use strict;
use diagnostics;

my @nums = qw/ 1 23 5432 45 /;
my $max_length = shift reverse sort map {1 + log($_)/log(10)} @nums;
printf "%d\n", $max_length;

I get the error

Experimental shift on scalar is now forbidden at - line 5, near "@nums;"

Why does shift think that it's being passed a parameter with scalar context, but reverse and sort don't?

Thanks

2 Answers

Note how the docs for shift say

shift ARRAY
shift

Given that you aren't giving it an array (a variable) but an expression it dismisses it right away.

They later have a comment (my emphasis)

Starting with Perl 5.14, an experimental feature allowed shift to take a scalar expression. This experiment has been deemed unsuccessful, and was removed as of Perl 5.24.

Clearly you are running 5.24 or newer version.

To understand the error message see docs for an older version, up until 5.22.4, with

shift ARRAY
shift EXPR
shift

Note that there is no LIST there. They continued to explain what that EXPR stands for

Starting with Perl 5.14, shift can take a scalar EXPR, which must hold a reference to an unblessed array. The argument will be dereferenced automatically. This aspect of shift is considered highly experimental.

So an older version, seeing an expression, would have computed it in the scalar context and passed that scalar to shift. (Then it would've complained about it not being an array reference.) Still having "shift on scalar" mentioned is meant to be informative but may be just confusing at this point.

Note that the length of a non-negative integer can be had by taking length of that string

my @lengths = reverse sort map { length } @nums;

what is far more efficient than taking logarithms.

Also, if you really need only the longest one

use List::Util qw(max);

my $max_length = max map { length } @nums;

what in this case can be done yet more efficiently as

my $max_length = length max @nums;

The following code achieves the OP's stated aim: "I'm trying to determine the maximum number of digits of any number in a list." He gets that maximum by taking the first element of an ephemeral array---an array that exists only for the duration of the sort statement.

The solution below does not answer the specific question about shift. But since the OP's array is anonymous (or defined implicitly---is it proper to call it anonymous?), and since it only exists for the duration of a single statement, the fact that shift removes the first element of an array means nothing here. What the OP wants is the value of the 1st element of the array.

#!/usr/bin/env perl
use strict; use warnings;
my @nums = qw/ 1 23 5432 45 /;
printf "%d\n", [reverse sort map {1 + log($_)/log(10)} @nums]->[0];

This also solves a more general problem: How to get the kth element of an array, when the array is anonymous or has only been defined implicitly.

A review, requesting improvement of this answer, suggested "explain how it's better / different than existing answers, if there are any."

Currently, the search term "how to find the kth element of an array defined implicitly" on stackoverflow brings up absolutely nothing. Same for "how to find the kth element of an anonymous array". So this provides an answer to that query.

For instance, if you want to know the last of the sorted keys of an array, you can do it in one statement thus:

#!/usr/bin/env perl
use strict; use warnings;
my %hash=(
   dog=>55,
   cat=>-99,
   'fleas bacteria'=>1e3,
   Romulus=>undef,
);
my $lastkey=[sort keys %hash]->[-1];

Related