Reputation: 4325
I don't understand the error message
Type of argument to each on reference must be unblessed hashref or arrayref at test.pl line 17.
for this test case (extracted from a larger module):
BEGIN { our $RE_timestamp = qr/whatever/; }
# check Regular expressions for TIMESTAMP
use constant _TEST_TIMESTAMPS => ( # from "6.2.3.1. Examples"
'1985-04-12T23:20:50.52Z' => 1,
'1985-04-12T19:20:50.52-04:00' => 1,
'2003-10-11T22:14:15.003Z' => 1,
'2003-08-24T05:14:15.000003-07:00' => 1,
'2003-08-24T05:14:15.000000003-07:00' => 0, # invalid!
'-' => 1 # NILVALUE
);
UNITCHECK
{
# from "6.2.3.1. Examples"
while (my ($str, $valid) = each _TEST_TIMESTAMPS) {
if ($valid) {
die "timestamp ($str) is not valid\n"
if ($str !~ /^${RE_timestamp}$/o);
} else {
die "timestamp ($str) is valid\n"
if ($str =~ /^${RE_timestamp}$/o);
}
}
}
Most of all I got the error in Perl 5.18.2, but when I checked on another machine using Perl 5.26.1, there was no error message at all!
So can I make the code work with the older Perl 5.18, too?
Experimenting I found out that each (my %h = _TEST_TIMESTAMPS)
did not help, but when using my %h = _TEST_TIMESTAMPS;
and then each %h
, then the error was gone.
Still I don't understand what's going on (before using constants I had local my
hashes used inside UNITCHECK
.
Obviously I'd like to use package-level constants instead.
Upvotes: 1
Views: 122
Reputation: 66883
That _TEST_TIMESTAMPS
is given as a "list constant" in the question and will behave only as a flat list, not as a hash. See this post for a very detailed discussion. Also, as such it is rejected by each
as the error message informs us.
One can use a hash-reference instead
use constant _TEST_TIMESTAMPS => { ... };
and this is accepted by each
. It appears to work correctly for the question's snippet but I'd be careful with more involved use. Using references with constant
comes with its own issues.
Also, keep in mind that objects from constant pragma are really subroutines ("in the current implementation", as docs say); see Technical Note
and Bugs
. This can affect what one should expect of their behavior in various circumstances.
One the other hand, swapping const
for, say, Const::Fast
, makes it work cleanly under all circumstances with normal lexical variables
use warnings;
use strict;
use feature 'say';
use Const::Fast;
our $RE_timestamp;
BEGIN { our $RE_timestamp = qr/whatever/; }
# check Regular expressions for TIMESTAMP
our %_TEST_TIMESTAMPS;
BEGIN {
const our %_TEST_TIMESTAMPS => ( # from "6.2.3.1. Examples"
'1985-04-12T23:20:50.52Z' => 1,
'1985-04-12T19:20:50.52-04:00' => 1,
'2003-10-11T22:14:15.003Z' => 1,
'2003-08-24T05:14:15.000003-07:00' => 1,
'2003-08-24T05:14:15.000000003-07:00' => 0, # invalid!
'-' => 1 # NILVALUE
);
}
UNITCHECK
{
# from "6.2.3.1. Examples"
while (my ($str, $valid) = each %_TEST_TIMESTAMPS) {
if ($valid) {
die "timestamp ($str) is not valid\n"
if ($str !~ /^${RE_timestamp}$/o);
} else {
die "timestamp ($str) is valid\n"
if ($str =~ /^${RE_timestamp}$/o);
}
}
}
Lexicals introduced with Const::Fast
must be assigned at declaration (and of course cannot be reassigned later), so here that has to be an our
variable as it need be assigned inside of a BEGIN
block but declared outside of it, so to be set and visible for UNITCHECK
.†
I use Const::Fast merely as my preference; another viable library is Readonly.
Note that $RE_timestamp
must be first lexically declared outside of any blocks if strict
is to be used (and why wouldn't one use it?). I corrected that. It need not be our
for any of this but I leave that since there may be other reasons for it.
As for why this isn't an issue in later Perls, I suppose that the requirement for it to be a hashref or arrayref got dropped at some point. (I can't check that right now.)
† That we can formally declare a variable at multiple places is a property of the lexical alias of a global variable, our.
Upvotes: 3
Reputation: 4325
The problem seems to be each
insisting on a hash variable (not a constant), so I solved it like this (not genuine, but it works):
UNITCHECK
{
my $hr = { _TEST_TIMESTAMPS };
while (my ($str, $valid) = each %$hr) {
#...
}
}
However a new temporary hash will be created by { _TEST_TIMESTAMPS }
, so it might be better to assign a hash reference to the constant instead.
Then you'd still need a variable, but no temporary hash object would be created.
Upvotes: 0