Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

&&= vivify keys?

by oha (Friar)
on Apr 18, 2012 at 08:49 UTC ( #965674=perlquestion: print w/replies, xml ) Need Help??

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

Hi,

$x{$k} &&= $v;

I expected the previous code to modify the hash only if the key is already there (and with a true value), or leave the hash untouched otherwise.

Instead it add the key also if it's missing, but with undef value.

$ perl -E 'use Data::Dumper; my %x; $x{a}&&=1; say Dumper(\%x);' $VAR1 = { 'a' => undef };
Am I missing something?

TIA

This is perl 5, version 12, subversion 4 (v5.12.4) built for x86_64-li +nux-gnu-thread-multi

Replies are listed 'Best First'.
Re: &&= vivify keys?
by Eliya (Vicar) on Apr 18, 2012 at 09:06 UTC

    $x{a} &&= 1; is effectively the same as

    $x{a} = $x{a} && 1;

    which is in this case

    $x{a} = undef && 1;

    which is

    $x{a} = undef;
      so true!

      not sure why, but i was expecting it to be the same as

      $x{a} && $x{a} = 1;
      thank you!

        Actually, you're closer to the truth than Eliya is. The assignment is indeed conditional.

        Aside from the syntax error from missing parens, the problems both yours and Eliya share are:

        • 1 is actually evaluated before anything else,
        • $x{a} is only evaluated once, and
        • $x{a} is (only) evaluated in lvalue context.

        See Re^2: &&= vivify keys?.

Re: &&= vivify keys?
by dada (Chaplain) on Apr 18, 2012 at 09:49 UTC

    while the explanation of Eliya++ pretty much solves the issue, I noticed a strange behaviour. if I do exactly the same but using a tied hash, Perl seems to parse the instruction differently. no key with undef value is created at all.

    #!/usr/bin/env perl use 5.010; use warnings; use strict; use Tie::Hash; use Data::Dumper; package DEBUGHASH; use base 'Tie::Hash'; sub TIEHASH { my $self = bless {}, shift; warn "TIEHASH $self\n"; return $self; } sub STORE { my($self, $key, $val) = @_; warn "STORE $self $key=$val\n"; $self->{$key} = $val; } sub FETCH { my($self, $key) = @_; warn "FETCH $self $key\n"; return $self->{$key}; } sub EXISTS { my($self, $key) = @_; warn "EXISTS $self $key\n"; return exists $self->{$key}; } sub FIRSTKEY { my($self) = @_; warn "FIRSTKEY $self\n"; my $key = scalar keys %{$self}; return each %{$self}; } sub NEXTKEY { my($self, $lastkey) = @_; warn "NEXTKEY $self $lastkey\n"; return each %{$self}; } package main; my %x; tie %x, 'DEBUGHASH'; $x{a} &&= 1; # this one, instead, generates the 'a' => undef key # $x{a} = $x{a} && 1; say Dumper(\%x); # just to check that the hash really works... $x{b} = 42; say Dumper(\%x);

    running the code above I get:

    TIEHASH DEBUGHASH=HASH(0x649f78) FETCH DEBUGHASH=HASH(0x649f78) a FIRSTKEY DEBUGHASH=HASH(0x649f78) $VAR1 = {}; STORE DEBUGHASH=HASH(0x649f78) b=42 FIRSTKEY DEBUGHASH=HASH(0x649f78) FETCH DEBUGHASH=HASH(0x649f78) b NEXTKEY DEBUGHASH=HASH(0x649f78) b $VAR1 = { 'b' => 42 };

    probably not worth further investigation, but curious nonetheless :-)

    cheers,
    Aldo

    King of Laziness, Wizard of Impatience, Lord of Hubris

      It's because what Eliya said isn't completely true. «$x{a} &&= 1;» isn't exactly equivalent to «$x{a} = $x{a} && 1;». Specifically, «$x{a}» is never assigned to itself. What really happens:

      1. The RHS of the assignment (1) is evaluated.
      2. The LHS of the assignment ($x{a}) is evaluated in lvalue context (which vivifies the hash element). [FETCH]
      3. If the scalar from the LHS is false,
        1. The scalar from the RHS is assigned to the scalar returned by the LHS. [STORE]

      Notice how the store is conditional? The autovivification actually occurs on fetch.

      The reason your tie code doesn't behave like the non-tie code is that you didn't autovivify the inner hash on fetch like the non-tie code does (step 2). But that's excusable -- I don't know if you can even tell you were called in lvalue context.

        Thanks for the clarification.

        As for

        3. If the scalar from the LHS is false, ...

        I think that should be

            3. If the scalar from the LHS is true, ...

        How else could a true value in the scalar ever change?

        $ perl -E'$x=1; $x &&= 0; say $x' 0

      It has been my experience that “tied hashes” don’t necessarily work as ordinary true hashes do.   I don’t profess to know the perlguts of it, and it seems to be somewhat implementation-dependent.

      My approach in any case would simply be to test whether the key exists or not, with an if-statement.   That should work in every case and at the same time be perfectly clear.

        I know it's many years late but I would like to know exactly in what ways you have noticed tied hashes acting differently.

Re: &&= vivify keys? (for or)
by tye (Sage) on Apr 18, 2012 at 13:15 UTC
    $_ = $v for $h{$k} || ();

    Just in case $h{$k} actually involves some long variable names or even some complex expressions.

    - tye        

Re: &&= vivify keys?
by Anonymous Monk on Apr 18, 2012 at 09:04 UTC
    Zero bool-and anything is Zero, same holds for undef.

    You were looking for bool-or.

Re: &&= vivify keys?
by Anonymous Monk on Apr 18, 2012 at 09:18 UTC

    I expected the previous code to modify the hash only if the key is already there (and with a true value) .... Am I missing something?

    Consider

    $ perl -MData::Dump -e " $foo = 1; dd $foo; $foo &&= 5; dd $foo; " 1 5 $ perl -MData::Dump -e " $foo = undef; dd $foo; $foo &&= 5; dd $foo; " undef undef

    $foo exists regardless of the value it has. Similarly $foo{bar} exists by the virtue that you try to assign a value to it (l-value context).

    Its like writing  $foo{bar} = ( $foo{bar} && 5 );

Re: &&= vivify keys?
by jwkrahn (Monsignor) on Apr 18, 2012 at 19:33 UTC
    $x{$k} &&= $v;

    I expected the previous code to modify the hash only if the key is already there (and with a true value), or leave the hash untouched otherwise.

    The problem is that your code is testing the value of the hash.    In order to test the hash key you have to use the exists operator.    From your description you want something like this:

    $x{$k} = $v if exists $x{$k} && $k;

      The OP does indeed want to test the value ("and with a true value"). The following would serve his needs:

      $x{a} = 1 if !$x{a};

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (4)
As of 2022-01-24 14:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    In 2022, my preferred method to securely store passwords is:












    Results (64 votes). Check out past polls.

    Notices?