Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

exists() unexpected behavior

by Richard (Initiate)
on Apr 12, 2001 at 03:20 UTC ( #71897=perlquestion: print w/replies, xml ) Need Help??

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

Should exists() add elements to the hash it queries? It seems to in this script:
#!/usr/bin/perl # test perl script # check behavior of "exists" # reported to activestate as bug 1747 4/11/01 %test_hash = () ; $key1 = 'x' ; $key2 = 'y' ; if ( exists $test_hash{$key1} ) { print "The key exists\n" ; } else { print "The key does not exist\n" ; } if ( exists $test_hash{$key1}{$key2} ) { print "The double key exists\n" ; } else { print "The double key does not exist\n" ; } if ( exists $test_hash{$key1} ) { print "The key exists now\n" ; } else { print "The key still does not exist\n" ; } # end of script ---------------------------
The result I get is:
The key does not exist The double key does not exist The key exists now
I tried this on ActiveState build 620 and Sun OS version 5.000. Is this a bug or do I just not understand?

Replies are listed 'Best First'.
Re: exists() unexpected behavior
by dws (Chancellor) on Apr 12, 2001 at 03:26 UTC
    When you make the test
    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.

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
Re: exists() unexpected behavior
by merlyn (Sage) on Apr 12, 2001 at 03:25 UTC
      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.

        See the other replies for why Perl does this. I have a subroutine I use when I want to get a value from a nested structure only if it exists. You can modify this to do the existence check only if you need that.

        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

        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

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'}) { ... }
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:

    1. exists $test_hash{$key1}
    2. exists $test_hash{$key1}{$key2}
    3. 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...

      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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://71897]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (2)
As of 2023-02-04 06:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    I prefer not to run the latest version of Perl because:







    Results (31 votes). Check out past polls.

    Notices?