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

I have discovered some interesting behavior with the exists() command when testing multi-keyed hashes. Given the following hash:
my %List; $List{"a"}{"1"} = "yes"; $List{"a"}{"2"} = "no"; $List{"b"}{"1"} = "possibly"; $List{"b"}{"2"} = "never";
If I use exists() to test for a subkey, it creates a new instance. To demonstrate, use the above hash with this code:
print "Before:\n"; foreach my $priKey (sort keys %List) { print "$priKey\n"; } if (exists $List{"c"}{"1"} ) { print "found a bad one\n"; } print "After:\n"; foreach my $priKey (sort keys %List) { print "$priKey\n"; }
This will give the output:
Before: a b After: a b c
Is this correct behavior that I need to code around or is it a bug? It seems to me that exists should never create a key, being a test-only operation, but I wanted to see if the Monks agree. The perl version used for the above is 5.6.1 on Solaris, but it appears to be acting the same way on other versions.

Replies are listed 'Best First'.
Re: Unexpected exists() behavior
by davido (Cardinal) on Jun 22, 2006 at 16:42 UTC

    You cannot test for {c}{1}'s existence without {c} existing first, and if you do test for {c}{1}'s existence when {c} itself does not yet exist, it will spring into existence so that {1} can be tested. This is one of the common pitfalls of autovivification. The obvious solution is to only check for {1} after you know {c} exists.


    Dave

      In this case, change
      exists $List{"c"}{"1"}
      to
      exists $List{"c"} and exists $List{"c"}{"1"}

Re: Unexpected exists() behavior
by Fletch (Bishop) on Jun 22, 2006 at 16:29 UTC
Re: Unexpected exists() behavior
by jeffa (Bishop) on Jun 22, 2006 at 16:18 UTC

    Yes, that is correct behavior, try using defined and/or length instead. (see update instead)

    UPDATE: actually ... those will not work the way you think they will either. Usually one only needs to test for the value of a key, not the key itself. Try greping the keys from the hash, or use a foreach on the keys to see if the key in question is there or not.

    Furthermore, try checking only if (exists($List{c}) ) ... exists is not autovivifying the key "c" ... checking $List{c}->{1} is what is causing the key "c" to be created.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Testing for hash{priKey} before the full test is my workaround solution already in the works. It just seemed odd to me that a function with the sole purpose of testing would create an entry under the correct circumstances. Any idea when / why that would be desired behavior?
Re: Unexpected exists() behavior
by Moron (Curate) on Jun 23, 2006 at 15:24 UTC
    Actually, the docs say:
    undef $ref; if (exists $ref->{"Some key"}) { } print $ref; # prints HASH(0x80d3d5c)
    "This surprising autovivification in what does not at first--or even second--glance appear to be an lvalue context may be fixed in a future release."

    Translating 'autovivication' from the ancient Latin I get: "of bringing itself to life," so I take it to mean "self-define" in the context.

    As for a workaround, I would expect using 'defined()' instead of 'exists()' ought to do it.

    -M

    Free your mind