Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

gotchas with hash element autovivification

by raybies (Chaplain)
on Apr 02, 2012 at 22:58 UTC ( #963142=perlquestion: print w/ replies, xml ) Need Help??
raybies has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to get this clear in my brain so I can explain it to a bunch of folk who have never seen Perl before...

I seemed to recall a long while ago that if you accessed a hash element that didn't exists it would autovivify the element... even if testing it with defined, and that you want to use exists... but looking through current documentation it seems that some of that warning has been curtailed. So I'm a little foggy as to what is the warning related to creating unintended hash keys and autovivification. Sorry if I'm really vague about version #'s and details... I may have misremembered things.

Are there conditions in which you can still create autovivified hash keys with undefined values that you never intended to do? And what is the way to avoid that now with current versions of Perl?

I wrote a little script to test it, because I thought this used to fail.

print "Oh No!(1)" if $rec{NOTE} eq "Beware!"; print "Oh No!(2)" if exists $rec{NOTE};
I don't get either "Oh No!" comments. I thought once upon a time it did... But with Nested Hashes I still get the intermediate hash autovivified.
print "Oh No!(3)" if exists $rec{NOTE}{Nested}; print "Oh No!(4)" if exists $rec{NOTE}; OUTPUT: Oh No! (4)

So I guess if you have nested Hashes it creates an empty hash pointer for the NOTE key.

Comment on gotchas with hash element autovivification
Select or Download Code
Re: gotchas with hash element autovivification
by JavaFan (Canon) on Apr 02, 2012 at 23:39 UTC
    Are there conditions in which you can still create autovivified hash keys with undefined values that you never intended to do? And what is the way to avoid that now with current versions of Perl?
    Nothing has changed in this aspect in the 21st century. $rec{NOTE} did not autovivify in 5.005, and it doesn't in 5.15.9. exists $rec{NOTE}{Nested} autovivified $rec{NOTE} in 5.005, and it does so in 5.15.9.

    It probably wasn't different pre-5.005, but I don't have such a perl currently available.

    If you want to avoid it? Use long hand:

    if ($rec{NOTE} && exists $rec{NOTE}{Nested}) { ... }
      if ($rec{NOTE­} && exists $rec{NOTE}­{Nested}) {

      More-DRY versions:

      if( exists ${ $rec{NOTE­} || {} }{Nested} ) { # or if( ( $rec{NOTE­} || {} )->{Nested} ) { # or if( exist +( $rec{NOTE­} || {} )->{Nested} ) {

      Though, I find that I use those less often to avoid auto-viv and more often to prevent a potential fatal "Can't use undef as HASH ref" (something that I think should at least be turned into just a warning -- since sometimes it is fatal and sometimes it is a silent success, and I'm not even sure what distinguishes between those two behaviors).

      - tye        

        It's a matter of opinion, but I prefer the simplicity of my suggestion, even if there's repetition. Even though I sometimes use your technique (typically in cases like keys %{$hash{foo} || {}}), I still had to look twice.
Re: gotchas with hash element autovivification
by roboticus (Canon) on Apr 02, 2012 at 23:50 UTC

    raybies:

    The gotcha is this: before exists or defined can operate on the hashref, perl has to navigate through the data structure to get to the hash reference, and it creates 'em as needed to get there. For example:

    $ cat autovivify.pl #!/usr/bin/perl use strict; use warnings; use Data::Dump qw(dump); my %h = (A=>0); print "Foo" if defined $h{Apple}{Banana}{Seahorse}; print dump(\%h); $ perl autovivify.pl { A => 0, Apple => { Banana => {} } }

    As I understand it[1], under the hood, the defined operator wants a scalar to test. So perl has to navigate through the hash to get *to* that scalar. In this case, it had to create a hash reference keyed from Apple, and inside that it needed to create another hash reference keyed from Banana. Now perl has a scalar value (the empty hash reference pointed to by Banana), and passes it to defined who's checking for the definedness of the Seahorse entry.

    Notes:

    [1] I've never spelunked in the perl source code, but I've studied compilers, and have an inkling of how things might go.

    [2] I was surprised that there wasn't a Seahorse entryhash as well. Before testing it, I would've expected to see a Seahorse entry as well. But it's clearly not there!

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

    Update: Fixed, ++ to tobyink for the catch.

      I was surprised that there wasn't a Seahorse hash as well. Before testing it, I would've expected to see a Seahorse entry as well. But it's clearly not there!

      Of course there's no Seahorse hash. If it created a Seahorse hash, then it would have to print "Foo", and this would make the defined keyword kinda useless.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

        tobyink:

        Yes, but that's not what I said. I didn't say a Seahorse hash, I said Seahorse *entry*: Whoops! I certainly did!

        $ cat autovivify.pl #!/usr/bin/perl use strict; use warnings; use Data::Dump qw(dump); my %h = (A=>0); print "Foo" if defined $h{Apple}{Banana}{Seahorse}; $h{Apple}{Banana}{Seahorse}=undef; print "Bar" if defined $h{Apple}{Banana}{Seahorse}; print dump(\%h); $ perl autovivify.pl { A => 0, Apple => { Banana => { Seahorse => undef } } }

        As you can see, if the Seahorse entry exists defined can still return false.

        In my (clearly incorrect) mental model of hashes, the defined function and the perl compiler, defined would be passed the contents of the *value* slot of the Seahorse entry. So I expected that the Seahorse entry would be created so that the compiler could hand defined the undefined value. This is one of perl's irksome (to me) quirks.

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

        Update: Repaired, per tobyink's note.

Re: gotchas with hash element autovivification
by ikegami (Pope) on Apr 03, 2012 at 00:32 UTC

    I don't get either "Oh No!" comments.

    Then $rec{NOTE} exists, but contains a value other than "Beware!".

    But with Nested Hashes I still get the intermediate hash autovivified

    The following contains a dereference:

    $rec{NOTE}{Nested}

    You could have written it as follows:

    $rec{NOTE}->{Nested}

    If you factor in autovivification, then the above is equivalent to the following:

    ( $rec{NOTE} //= {} )->{Nested}

    To get autovivified, $rec{NOTE} has to be undefined (or nonexistent), which is consistent with what we already knew (existent and not equal to "Beware!").

    By the way, that means (1) would have given you a warning if you had warnings on as you should.


    use strict; use warnings; no warnings 'void'; my %rec; printf "exists: %s\n", exists($rec{NOTE}) ?1:0; printf "defined: %s\n", defined($rec{NOTE}) ?1:0; printf "true: %s\n", $rec{NOTE} ?1:0; print "--\n"; $rec{NOTE} = undef; printf "exists: %s\n", exists($rec{NOTE}) ?1:0; printf "defined: %s\n", defined($rec{NOTE}) ?1:0; printf "true: %s\n", $rec{NOTE} ?1:0; print "--\n"; $rec{NOTE}{Nested}; printf "exists: %s\n", exists($rec{NOTE}) ?1:0; printf "defined: %s\n", defined($rec{NOTE}) ?1:0; printf "true: %s\n", $rec{NOTE} ?1:0;
    exists: 0 defined: 0 true: 0 -- exists: 1 defined: 0 true: 0 -- exists: 1 defined: 1 true: 1
Re: gotchas with hash element autovivification
by tobyink (Abbot) on Apr 03, 2012 at 07:03 UTC

    If you want to avoid autovivification, there's the autovivification module. It allows you to disable autovivification for a particular scope. Example:

    use 5.010; use strict; use Data::Dumper; my %rec; { no autovivification; say "Oh No!(1)" if exists $rec{NOTE}{Nested}; say "Oh No!(2)" if exists $rec{NOTE}; } say "Oh No!(3)" if exists $rec{NOTE2}{Nested}; say "Oh No!(4)" if exists $rec{NOTE2}; print Dumper \%rec; __END__ Oh No!(4) $VAR1 = { 'NOTE2' => {} };

    It even allows you to selectively disable it - e.g. disable autovivification for "exists", but not when storing a new value.

    no autovivification qw(exists); if (!exists $rec{NOTE}{Nested}) { # but this still autovivifies $rec{NOTE}={}; $rec{NOTE}{Nested2} = 'Foo'; }

    And also allows you to make autovivification warn or die.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (11)
As of 2014-12-18 15:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (58 votes), past polls