tags:

views:

122

answers:

4

I have the following (idealised from a bug) short script in Perl:

my %metadata = undef;
if (defined %metadata)
{
    print "defined";
}

For some reason the output of the program is always "defined". So setting the hash to be "undefined" somehow makes it defined. Is it defined as being "undefined"?

EDIT:

This is an idealised case, in an attempt to replicate the problem. What I'm actually doing is more like:

my %metadata = my_sub_function();
if (defined %metadata)
{
    print "defined";
}

Where the output of my_sub_function may be undef, () or a populated hash, and I only want to print "defined" in the last of these cases.

EDIT 2:

Incidentally I have found that

if (scalar(keys %metadata)

behaves correctly for (), but still not for undef.

+9  A: 

Try

 my %metadata;
 undef %metadata;
 if (defined %metadata)
 {
     print "defined";
 }

I think it was just using a "undef" as data to populate the hash.

"use warnings" would have said:

defined(%hash) is deprecated at t.pl line 6.
    (Maybe you should just omit the defined()?)
Odd number of elements in hash assignment at t.pl line 4.
Use of uninitialized value in list assignment at t.pl line 4.

undef, () or a populated hash

If you really want to distinguish between an empty hash and no hash, better use a hash reference (also lighter to pass around from the subroutine, no pass-by-copy and all).

Thilo
ah, interesting! I haven't seen this error in my logs, and the code from which it comes does use strict and use warnings. I have also tried without defined(%hash), with the same results though.
Richard J
unfortunately I'm not in charge of what comes back from the sub function directly, so I have to work with what I'm given :)
Richard J
"Use of defined on aggregates (hashes and arrays) is deprecated. It used to report whether memory for that aggregate has ever been allocated. This behavior may disappear in future versions of Perl. You should instead use a simple test for size". So, your current approach might not work too well. Better talk to the people who made that subroutine.
Thilo
+1  A: 

try this

%metadata=("1"=>"one");
undef %metadata;
if ( %metadata)
{
    print "defined";
}

And if you look at the documentation on perldoc -f defined, it says this ways of checking is deprecated.

 Use of "defined" on aggregates (hashes and arrays) is deprecated. 
ghostdog74
+2  A: 

if you see the documentation of defined, you will find that ,

When defined used on a hash element, 
it tells you whether the value is defined, 
not whether the key exists in the hash. 

try simple, if (%a_hash) { print "has hash members\n" }

Note:undef is the value of a variable that has never been initialized (or that has been reset using the undef function). The defined function returns true if the value of the expression is not undef.

Nikhil Jain
+5  A: 

If your function returns undef and that undef is placed in the hash, auto-stringification changes that undef to be '' and you end up with: %metadata = ( '' => undef ).

If your function needs to return either undef, () or a proper hash and you need to test all three cases separately, I'd suggest:

my %metadata = my_sub_function();
if ( !scalar keys %metadata ) {
  print "An empty list () was returned\n"
} elsif (
  scalar keys %metadata == 1 and
  exists $metadata{''} and
  !defined $metadata{''}
) {
  print "undef or '' was returned\n";
} else {
  print "A hash was hopefully returned\n";
}

You could test it with the following my_sub_functions:

sub my_sub_function { return ()                 } # 1st case
sub my_sub_function { return undef              } # 2nd case
sub my_sub_function { return ( a => 1, b => 2 ) } # 3rd case
sub my_sub_function { return qw/not a hash/     } # unhandled case

Happy hacking

mfontani
Wonderful; thanks very much! Perl always catches me out with how easy it makes some hard things, and how hard some things that ought to be simple become :)
Richard J