Re: exists() unexpected behavior
by dws (Chancellor) on Apr 12, 2001 at 03:26 UTC
|
if ( exists $test_hash{$key1}{$key2} ) {
print "The double key exists\n" ;
}
you're not testing for whether a value exists for the two-part key $key1, $key2. Rather, you're testing for the existence of the key $key2 within the hash %test_hash{$key1}. The existence test implicitly creates the empty hash.
Should that be the way exists works? Good question. "Does this key exists in this hash?" is a subtley different question than "Does this hash exist, and does this key exist within it"? I'm not sure that extending exists to answer the latter is a good thing, given that means exist to answer the questions independently.
| [reply] [d/l] [select] |
Re: exists() unexpected behavior
by MeowChow (Vicar) on Apr 12, 2001 at 03:26 UTC
|
When you write the expression...
$test_hash{$key1}{$key2}
... the key1 hash value must be auto-vivified as a hash reference for a dereference to happen. However, exists will not auto-vivify the key2 hash-key which is actually being tested for.
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] [d/l] [select] |
Re: exists() unexpected behavior
by merlyn (Sage) on Apr 12, 2001 at 03:25 UTC
|
| [reply] |
|
Is this that hard to change? (A naive question coming from somebody who codes IN perl but doesn't code the perl) Autovivication is a great thing so that constructs such as $a->{b}->{c} = 'd' create the entire structure without much tedium. That being said, looking at a file existence check of (-e "/a/b/c/d")
or a system ls on a directory doesn't go about creating directories (I know, I know ... apples to oranges). Is this planned behavior. It seems that it would make good sense in an assignment operator, but that is all. Does it have to occur whenever it tries to back up the reference line?
OK, I've probably put my foot in my mouth because I haven't looked at the back end of Perl.
| [reply] [d/l] [select] |
|
use Carp;
sub safe_get {
my $handle = shift;
my $ptr = $handle;
for (my $i = 0; $i < @_; $i++) {
my $key = $_[$i];
# Handle each type
my $type = ref $ptr;
if (not defined $type) {
croak "Not a reference.\n";
}
elsif ($type eq 'HASH') {
return undef unless exists $ptr->{$key};
$ptr = $ptr->{$key};
}
elsif ($type eq 'ARRAY') {
croak "Non numeric array index '$key'."
unless $key =~ m/^\+?\d+$/;
croak "Bad array thing '$key'." unless $key >= 0;
return undef unless $key < @$ptr;
$ptr = $ptr->[$key];
}
else {
croak "Unable to handle type '$type'";
}
# Is this the end?
return $ptr if $i == @_ - 1;
}
}
# Usage
my %foo = {bar => [1, {baz => 'xyzzy'}] };
my $value1 = safe_get(\%foo, 'bar', 1, 'baz');
my $value2 = safe_get(\%foo, 'bar', 2, 'baz');
# $value1 is 'xyzzy', $value2 is undef
-ben
| [reply] [d/l] |
|
Well, technically, that's exactly what needs to be fixed, but it'd be hard to fix it. The dereferencing operator at the tail end knows if it's in an lvalue (being assigned to) or rvalue (being accessed) context, but apparently, the information is not available to the dereferencing operators in the middle of the chain, and that'd be messy to add. Apparently, it requires adding a new bytecode to Perl, and that has a lot of potential for breaking many more things than it fixes.
For now, the known workaround is to step carefully down the tree, calling exists at each level before proceeding to the next. In practice, I've not been bitten by this, but maybe it's because I had been bitten once or twice while practicing with references. {grin}
-- Randal L. Schwartz, Perl hacker
| [reply] |
Re: exists() unexpected behavior
by MrNobo1024 (Hermit) on Apr 12, 2001 at 05:16 UTC
|
When you access part of a multidimentional hash, even if you're only using exists(), it creates all the keys except the last one. So if you call exists $foo{'bar'}{'baz'}{'qux'}, it creates $foo{'bar'} and $foo{'bar'}{'baz'}, but not $foo{'bar'}{'baz'}{'qux'}.
If you don't want that to happen, you have to test every level, starting with the top:
if(exists $foo{'bar'} and exists $foo{'bar'}{'baz'} and exists $foo{'b
+ar'}{'baz'}{'qux'}) {
...
}
| [reply] [d/l] [select] |
Re: exists() unexpected behavior
by ton (Friar) on Apr 12, 2001 at 03:35 UTC
|
Ahh, the follies of not using strict...
These are the three crucial calls in your routine:
- exists $test_hash{$key1}
- exists $test_hash{$key1}{$key2}
- exists $test_hash{$key1} # again
During the first call, you test whether or not $test_hash contains a key
called $key1. Of course it doesn't, so exists returns false.
During the second call, you first grab hash reference stored as a value in $test_hash with key $key.
This doesn't exist, so Perl dutifully creates it for you on the fly (the behavior when you don't use strict).
You then check to see if this anonymous hash contains a key equal to $key2, which it doesn't.
Since you created an anonymous hash reference on the fly in step 2, $test_hash{$key1} now contains that reference. So exists returns true.
Thus, exists() did not fill the value for $test_hash{$key1}; you implicitly did, when you did a lookup on a hash reference
that previously did not exist. This would become apparent had you used strict.
The motto of the story is always use strict!
UPDATE: As MeowChow correctly points out, use strict does not catch this. Neither does -w. Oops. You should still always use strict, though :)
-Ton
-----
Be bloody, bold, and resolute; laugh to scorn
The power of man... | [reply] |
|
Actually, this hasn't got anything at all to do with strict. Strict would not report these as errors because they are not. They are examples of a feature of Perl which, as I mentioned in my earlier node, is called autovivification.
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] |