norbert.csongradi has asked for the wisdom of the Perl Monks concerning the following question:

Dear fellow Monks,

I have an interesting example code showing that autovivification could happen accidentally, even hidden for the user, when hash slices are passed to a user defined subroutine, but not when passed to a built-in function. How (and why) does it happen?

sub mydump($) { print Data::Dumper->Dump( [ $_[0] ], ['*hash'] ); } sub myjoin($@) { return join @_; } my %hash; my $s1 = join( ':', @hash{qw(key)} ); # line no. 12 mydump( \%hash ); my $s2 = myjoin( ':', @hash{qw(key)} ); mydump( \%hash );
Output is:
$ perl -w ./hashslices.pl Use of uninitialized value within %hash in join or string at ./hashsli +ces.pl line 12. %hash = (); %hash = ( 'key' => undef );

Replies are listed 'Best First'.
Re: Autovivification and hash slices
by moritz (Cardinal) on Aug 17, 2011 at 12:24 UTC
    You ignore that join also has a prototype. When you write
    join @_;

    Then the @_ is evaluated in scalar context as the separator, with an empty list of items to join.

    So join and myjoin evaluate their arguments differently, and it's no surprise that they lead to difference in autovivification.

      OK, but how to fix that and how can I avoid autovivification of the original hash?

      return (join (@_)); Same result.

      return join {@{\@_}) Same result.

      my ($sep, @a) = @_; return join($sep, @a);
      More warnings, but same result (original hash modified, but shouldn't).

Re: Autovivification and hash slices
by thundergnat (Deacon) on Aug 17, 2011 at 13:46 UTC

    The problem is that the parameters being passed to the user subroutine are being evaluated as they are passed, so the autovivify has already been done before the subroutine receives them. Apparently, (some wild-assed guess here) the built in join has some magic to check for existence before trying to evaluate.

    If you pass a hash reference and key value separately to your myjoin, you have a chance to check for existence before the key can be autovivified. It isn't as slick as the builtin, but it's not too bad.

    use Data::Dumper; sub mydump($) { print Data::Dumper->Dump( [ $_[0] ], ['*hash'] ); } sub myjoin { return unless exists($_[1]->{$_[2]}); return join $_[0], $_[1]->{$_[2]}; } my %hash; my $s1 = join( ':', @hash{qw(key)} ); mydump( \%hash ); my $s2 = myjoin( ':', \%hash, 'key' ); mydump( \%hash );
      Yeah, that is clear :). My problem is the modification of the original hash without touching the hash itself... and it only happens with hash slices :(

        Ah. Sorry, kind of glossed over the "slice" aspect there. All right, try this then.

        use Data::Dumper; sub mydump($) { print Data::Dumper->Dump( [ $_[0] ], ['*hash'] ); } sub myjoin { my ($sep, $href, @keys) = @_; my @exists = grep { exists($href->{$_}) } @keys; return join $sep, @exists; } my %hash = (key1 => 'a'); my $s1 = join( ':', @hash{qw(key)} ); mydump( \%hash ); my $s2 = myjoin( ':', \%hash, qw/key1 key2 key3/ ); mydump( \%hash );
Re: Autovivification and hash slices (rvalue)
by tye (Sage) on Aug 17, 2011 at 17:12 UTC

    Autovivification happens in an lvalue context (a place where a variable might be modified, not just 'read'). Arguments to user subroutines can be modified (but usually aren't). Perl knows that the join built-in doesn't modify the arguments passed to it. Perl 5 does not provide a way for you to declare that a user subroutine refuses to modify any arguments, nor does Perl try to analyze subroutines to determine this and thus change how arguments are prepared to be passed in.

    So don't put your slice into an lvalue context. Many ways to do that, including:

    myjoin( @_= @hash{@keys} ); myjoin( my @x= @hash{@keys} ); myjoin( @{[ @hash{@keys} ]} );
    myjoin( map $_, @hash{@keys} );
    myjoin( map $hash{$_}, @keys );

    - tye        

      Scratch that last:

      >perl -E"sub {}->( map $_, @h{x} ); say exists($h{x}) || 0; 1

      map aliases $_ to its operands, so its operands are lvalues. do{} creates copies.

Re: Autovivification and hash slices
by ikegami (Patriarch) on Aug 17, 2011 at 17:41 UTC

    If you use a hash slice in lvalue context, Perl must create the elements of the slice in case they will actually be modified.

    >perl -E"1 for @h{x}; say exists($h{x}) || 0; 1 >perl -E"sub {}->( @h{x} ); say exists($h{x}) || 0; 1 >perl -E"my $r = \@h{x}; say exists($h{x}) || 0; 1

    The for loop might assign to $_ with the intention of changing $h{x}, so a copy can't be used.

    The sub might assign to $_[0] with the intention of changing $h{x}, so a copy can't be used.

    $$r must refer to $h{x}, so a copy can't be used.

    As for join, it's a builtin operator (just like ("+"), not a sub call. Perl knows it doesn't need for lvalues, so it provide any. That's not the case for subs, though. Perl has no idea if the sub will pass values back by reference, so it much create lvalues just in case.

    Sidebar

    One exception!

    >perl -E"1 for $h{x}; say exists($h{x}) || 0; 1 >perl -E"sub {}->( $h{x} ); say exists($h{x}) || 0; 0 >perl -E"\@h{x}; say exists($h{x}) || 0; 1

    When a hash element (not a hash slice) is used as a sub argument, a special magical value is returned. Assigning to that magical value assigns to the hash. Getting a reference to magical value gets a reference to the hash element. Both of these cause the element's vivification, but not before. It's a bit slower to create and use this magical value, but it prevents a lot of accidental hash key vivifications.

    The special magical value isn't returned for other lvalue contexts, and hash slices aren't coded to ever return these magical values.

    To avoid vivification of the hash elements, you must avoid using the hash slice in lvalue context. You can do that by making copies of the values returned by the slice.

    >perl -E"sub {}->( @{[ @h{x} ]} ); say exists($h{x}) || 0;" 0 >perl -E"sub {}->( do { @h{x} } ); say exists($h{x}) || 0;" 0

    etc.

      Thank you very much, it clarifies it all :)
Re: Autovivification and hash slices
by Anonymous Monk on Aug 17, 2011 at 12:17 UTC
    What does happen when user sub prototype has list as optional instead of being required (if such a thing is possible at all)?
      Same, even when prototypes removed.