Search code examples
perlconstantseach

Why "Type of argument to each on reference must be unblessed hashref or arrayref at test.pl line 17."?


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?

Ugly Work-Around

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.


Solution

  • 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.