Sorry, this seems like such a basic question but I still don't understand. If I have a hash, for example:
my %md_hash = ();
$md_hash{'top'}{'primary'}{'secondary'} = 0;
How come this is true?
if ($md_hash{'top'}{'foobar'}{'secondary'} == 0) {
print "I'm true even though I'm not in that hash\n";
}
There is no "foobar" level in the hash so shouldn't that result i开发者_StackOverflown false?
TIA
This isn't a multidimensional hash specific question.
It works the same with
my %foo;
if ( $foo{'bar'} == 0 ) {
print "I'm true even though I'm not in that hash\n";
}
$foo{'bar'}
is undef, which compares true to 0, albeit with a warning if you have warnings enabled as you should.
There is an additional side effect in your case; when you say
my %md_hash = ();
$md_hash{'top'}{'primary'}{'secondary'} = 0;
if ( $md_hash{'top'}{'foobar'}{'secondary'} == 0 ) {
print "I'm true even though I'm not in that hash\n";
}
$md_hash{'top'}
returns a hash reference, and the 'foobar' key is looked for in that hash. Because of the {'secondary'}
, that 'foobar' element lookup is in hash-dereference context. This makes $md_hash{'top'}{'foobar'}
"autovivify" a hash reference as the value of the 'foobar' key, leaving you with this structure:
my %md_hash = (
'top' => {
'primary' => {
'secondary' => 0,
},
'foobar' => {},
},
);
The autovivification pragma can be used to disable this behavior. People sometimes assert that exists() has some effect on autovifification, but this is not true.
You are testing an undefined value for numeric zero. Of course you get a true result! What were you expecting?
You should also get a warning under use warnings
. Why didn’t you?
If you do not start a program with:
use v5.12; # or whatever it is you are using
use strict;
use warnings;
you really shouldn’t even bother. :)
EDIT
NB: I am only clarifying for correctness, because the comment lines are not good for that. I really could not possibly care one whingeing whit less about the reputation points. I just want people to understand.
Even under the CPAN autovivification
module, nothing changes. Witness:
use v5.10;
use strict;
use warnings;
no autovivification;
my %md_hash = ();
$md_hash{top}{primary}{secondary} = 0;
if ($md_hash{top}{foobar}{secondary} == 0) {
say "yup, that was zero.";
}
When run, that says:
$ perl /tmp/demo
Use of uninitialized value in numeric eq (==) at /tmp/demo line 10.
yup, that was zero.
The test is the ==
operator. Its RHS is 0
. Its LHS is undef
irrespective of autovivification. Since undef
is numerically 0
, that means that both LHS and RHS contain 0
, which the ==
correctly identifies as holding the same number.
Autovivification is not the issue here, as ysth correctly observes when he writes that “This isn’t a multidimensional hash specific question.” All that matters is what you pass to ==
. And undef
is numerically 0
.
You can stop autoviv if you really, really want to — by using the CPAN pragma. But you will not ever manage to change what happens here by suppressing autoviv. That shows that it is not an autoviv matter at all, just an undef
one.
Now, you will get “extra” keys when you do this, since the undefined lvalue will get filled in on the way through the dereference chain. These are neccesarily all the same:
$md_hash{top}{foobar}{secondary} # implicit arrow for infix deref
$md_hash{top}->{foobar}->{secondary} # explicit arrow for infix deref
${ ${ $md_hash{top} }{foobar} }{secondary} # explicit prefix deref
Whenever you deref an lvaluable undef
in Perl, that storage location always gets filled in with the proper sort of reference to a newly allocated anonymous referent of the proper type. In short, it autovivs.
And that you can stop either by suppressing or else by sidestepping the autoviv. However, denying the autoviv is not the same as sidestepping it, because you just change what sort of thing gets returned. The overall expession is still fully evaluated: there is no automatic short-circuiting just because you suppress autoviv. That’s because autoviv is not the problem (and if it were not there, you would be really annoyed: trust me).
If you want short-circuiting, you have to write that yourself. I never seem to need to myself. In Perl, that is. On the other hand, C programmers are quite accustomed to writing
if (p && p->whatever) { ... }
And so you can do that, too, if you want to. However, it is pretty rare in my own experience. You almost have to bend over wrongwards in Perl for that ever to make a difference, as it is quite easy to arrange for your code not to change how it acts if there are empty levels.
Try a search on "Perl autovivification".
The hash values "spring into existence" when you first access them. In this case, the value is undef
, which when interpreted as a number is zero.
To test for existence of a hash value without auto-vivifying it, use the exists
operator:
if (exists $md_hash{'top'}{'foobar'}{'secondary'}
&& $md_hash{'top'}{'foobar'}{'secondary'} == 0) {
print "I exist and I am zero\n";
}
Note that this will still auto-vivify $md_hash{'top'}
and
$md_hash{'top'}{'foobar'}
(i.e. the sub-hashes).
[edit]
As tchrist points out in a comment, it is poor style to compare undef
against anything. So a better way to write this code would be:
if (defined $md_hash{'top'}{'foobar'}{'secondary'}
&& $md_hash{'top'}{'foobar'}{'secondary'} == 0) {
print "I exist and I am zero\n";
}
(Although this will now auto-vivify all three levels of the nested hash, setting the lowest level to undef
'.)
精彩评论