AnonymousPerl1 has asked for the wisdom of the Perl Monks concerning the following question:

I can't post exact code, due to it being confidential stuff, but for the first time in a few years of mucking about in Perl, I've found a behavior that caused me to raise an eyebrow. Wisdom welcome!

use Data::Dumper; $hash{keyA}{keyB}{keyC}{keyD}=1; if (exists($hash{keyA}{keyB}{keyE}{keyF})) { print "Hi\n"; } print Dumper (\%hash); if (exists($hash{keyA}{keyB}{keyE}{keyF})) { print "Hi\n"; }

The expected behavior should be "Hi" is never printed because keyE doesn't exist in the hash (and obviously keyF doesn't exist if keyE doesn't exist). However, what I'm seeing both by using ::Dumper and in foreach hash looping further along in the code is that $hash{keyA}{keyB}{keyE} is being "created" just by checking to see if {keyA}{keyB}{keyE}{keyF} exists, however even with another exists() call, "Hi" will not be printed.

If I instead do this:

if (exists($hash{keyA}{keyB}{keyE})) {
then everything is fine and dandy and behaves as expected (keyE doesn't get created).

Is exists() just a dangerous function to use for nested hashes? Or did I stumble on a bug?

Replies are listed 'Best First'.
Re: exists() for a hash child-key creates the parent key? Bug?
by choroba (Cardinal) on Dec 17, 2014 at 12:34 UTC
    exists works safely only for the last step. See autovivification if you don't want to type
    if ( exists $hash{keyA} and exists $hash{keyA}{keyB} and exists $hash{keyA}{keyB}{keyE} and exists $hash{keyA}{keyB}{keyE}{keyF} ) {
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
      Thank you. Looks like I'll just have to trim my code a bit to be more careful about this, since I can't include any packages that aren't distributed with Perl by default (script is deployed to multiple machines that other users manage).
Re: exists() for a hash child-key creates the parent key? Bug?
by QM (Parson) on Dec 17, 2014 at 13:30 UTC
    You may want to try no autovivification, though it seems to be XS, and has some performance caveats and exceptions listed.

    You can also replace the built-in exists, or just write a special one for specific cases.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

Re: exists() for a hash child-key creates the parent key? Bug?
by Anonymous Monk on Dec 17, 2014 at 12:38 UTC

    Documented in exists (excerpt):

    if (exists $ref->{A}->{B}->{$key}) { } if (exists $hash{A}{B}{$key}) { } ...

    Although the most deeply nested array or hash element will not spring into existence just because its existence was tested, any intervening ones will. Thus $ref->{"A"} and $ref->{"A"}->{"B"} will spring into existence due to the existence test for the $key element above. This happens anywhere the arrow operator is used, ...

Re: exists() for a hash child-key creates the parent key? Bug?
by ikegami (Patriarch) on Dec 17, 2014 at 15:49 UTC
    exists($hash{keyA}{keyB}{keyE}{keyF})
    is short for
    exists($hash{keyA}->{keyB}->{keyE}->{keyF})

    exists doesn't autovivify, but -> sure does. Add

    no autovivification;
Re: exists() for a hash child-key creates the parent key? Bug?
by Anonymous Monk on Dec 17, 2014 at 22:54 UTC
Re: exists() for a hash child-key creates the parent key? Bug?
by locked_user sundialsvc4 (Abbot) on Dec 18, 2014 at 00:02 UTC

    When I have a “chain of keys” to work through, I sometimes use a loop ... something like this (untested) example:

    my $found = 1; my $cursor = $hashref; for my $key ( qw/$key1 $key2 $key3/ ) { if (exists( $cursor->{$key} ) { $cursor = $cursor->{$key}; } else { $found = 0; last; // NOT FOUND - QUIT LOOKING NOW } } . . . if ($found) { // $cursor REFERS TO THE ELEMENT THAT WAS FOUND ... }

    I hope that the approach is fairly self-explanatory.   $cursor advances through the data-structure (hashref $hashref), $key by key, as long as the keys being sought exist.   Anytime a key is found not to exist, the loop ends early and $found becomes False.   If the loop completes and $found is still True, the entire chain of keys exists and $cursor now points to whatever’s at the end of it all.

      Even though there are a few nits I could pick with the (pseudo-)code, I'm not going to do that, because this is a step towards the kind of contribution I'd love to see more of. ++

        Even though there are a few nits I could pick with the (pseudo-)code, I'm not going to do that, because this is a step towards the kind of contribution I'd love to see more of. ++

        Not at all. Don't need sundialsvc4 to pull a Khen1950fx . Pure word bullpucky is easier to ignore than pretend code.