Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Re: Arrow Operator Question

by kcott (Archbishop)
on Mar 26, 2023 at 11:45 UTC ( #11151230=note: print w/replies, xml ) Need Help??


in reply to Arrow Operator Question

G'day sectokia,

[Note: $a (and $b) are special variables; it's best to only use these for their intended purposes to avoid unexpected side effects; I've replaced $a with $h in the code below. Also, '//undef' is completely superfluous: if the LHS is undef, use undef instead. :-)]

See Autovivification and Arrow Notation in perlref.

Perl will autovivify as needed. It requires 'c' to check for 'd', so that key is autovivified. The 'd' key either exists and has a value or it doesn't exist and the value is undef: no autovivification is needed here.

You can use exists() to check for 'c'; only attempting to get a value for 'd' if 'c' exists. That can become unwieldy when there are multiple levels of keys; if you think autovivification is a problem, simply allow it then delete() afterwards.

The following code has examples which demonstrate these points.

$ perl -E ' use Data::Dump; { say "*** Autovivification"; my $h = { b => {} }; dd $h; my $f = $h->{b}{c}; say $f // "undefined"; dd $h; my $g = $h->{b}{c}{d}; say $g // "undefined"; dd $h; } { say "*** No autovivification"; my $h = { b => {} }; dd $h; my $f = $h->{b}{c}; say $f // "undefined"; dd $h; my $g = exists $h->{b}{c} ? $h->{b}{c}{d} : undef; say $g // "undefined"; dd $h; } { say "*** Autovivify then delete"; my $h = { b => {} }; dd $h; my $f = $h->{b}{c}{x}{y}{z}; say $f // "undefined"; dd $h; delete $h->{b}{c}; dd $h; } '

Output:

*** Autovivification { b => {} } undefined { b => {} } undefined { b => { c => {} } } *** No autovivification { b => {} } undefined { b => {} } undefined { b => {} } *** Autovivify then delete { b => {} } undefined { b => { c => { x => { y => {} } } } } { b => {} }

— Ken

Replies are listed 'Best First'.
Re^2: Arrow Operator Question
by BillKSmith (Monsignor) on Mar 27, 2023 at 14:24 UTC
    I prefer to implement kcott's idea this way. "Short circuit" operation of the and operator C style Logical And prevents unnecessary testing which would cause autovivification.
    use strict; use warnings; use Data::Dumper; use Test::More tests=>2; my $h = { b => {} }; my $f = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d}); is_deeply( $h, { b => {} }, 'no autovivification' ); is( $f, undef, 'no value assigned' );

    OUTPUT:

    1..2 ok 1 - no autovivification ok 2 - no value assigned
    Bill
      my $f = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d});

      This is dangerous: the nature of my $var = ... if ... is predictable but very noninuitive (it acts something like a state variable). It has long been regarded as a bug that perl core would dearly love to fix, but cannot due to back-compat.

      I recommend always either splitting out the declaration from the assignment:

      my $f; $f = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d});

      .. or using state explicitly when that's what you actually intend:

      use feature 'state'; # 'use 5.10' or later also gives this automatica +lly state $f = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d +});
        I was a bit surprised that my my did work as I intended. I choose to use it in the interest of conforming to the original code as much as possible. Thanks for the explanation.
        Bill

      G'day Bill,

      That's certainly a valid way to go. Consider the following:

      $ perl -e ' use strict; use warnings; use Test::More tests => 8; my $h = { b => {} }; my ($f, $f2, $f3, $f4); $f = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d}) +; is_deeply( $h, { b => {} }, "no autovivification" ); is( $f, undef, "no value assigned" ); $f2 = $h->{b}{c}{d} if exists $h->{b}{c}; is_deeply $h, { b => {} }, "f2: no autovivification"; is $f2, undef, "f2: no value assigned"; $f3 = $h->{b}{c}{d}{x}{y}{z} if exists $h->{b}{c} and exists $h->{b}{c}{d} and exists $h->{b}{c}{d}{x} and exists $h->{b}{c}{d}{x}{y}; is_deeply $h, { b => {} }, "f3: no autovivification"; is $f3, undef, "f3: no value assigned"; $f4 = $h->{b}{c}{d}{x}{y}{z}; delete $h->{b}{c}; is_deeply $h, { b => {} }, "f4: autovivification removed"; is $f4, undef, "f4: no value assigned"; '

      Update: The code above is a modification of the original. Something was niggling me about what I first wrote, but I couldn't see the problem. ++hv's response to your post alerted me to the issue. My first, less-than-good effort is in the spoiler below. Note that the output is unchanged.

      Output:

      1..8 ok 1 - no autovivification ok 2 - no value assigned ok 3 - f2: no autovivification ok 4 - f2: no value assigned ok 5 - f3: no autovivification ok 6 - f3: no value assigned ok 7 - f4: autovivification removed ok 8 - f4: no value assigned
      • Short-circuiting is not actually needed in your example (see f2)
      • Short-circuiting works but can be unwieldy with more complex data structures (see f3)
      • Allowing autovivification then removing it results in cleaner, and easier to maintain, code (see f4)
        • This option has potential drawbacks. Consider the case where $h->{b}{c}{q} existed and had a meaningful and required value but is removed by 'delete $h->{b}{c}'.
        • On second thoughts: this is probably a very bad idea (even though it does work in this specific test script).

      I think each has its merits and probably comes down to best choice on a case by case basis.

      — Ken

        sectokia only showed us the fail case. I assume that his code worked as he expected in the pass case. I should have demonstrated that my code produced the same result in that case.
        use strict; use warnings; use Data::Dumper; use Test::More tests=>2; my $h = { b => { c => { d => 'value' } } }; my $g = $h->{b}{c}{d} if exists($h->{b}{c}) and exists($h->{b}{c}{d}); my $f = $h->{b}{c}{d}//undef; # Original Code is( $g, $f, $f ); is_deeply( $h, { b => { c => { d => 'value' } } }, 'no change' );

        The difference between your various f's is not merely one of style, but one of requirements (which we really do not know).

        Bill
Re^2: Arrow Operator Question
by sectokia (Pilgrim) on Mar 27, 2023 at 08:41 UTC

    Thanks. The reason I had undef was the style I generally use is more like this:

    if ($foo == ($bar->{c}{d}{e}//'moo')) { ... }
      "if ($foo == ($bar->{c}{d}{e}//'moo')) { ... }"

      You may have typed that in a hurry. While I do follow the gist of what you're saying, I do hope you're aware that '==' is used for numbers (e.g. 300) and 'eq' is used for strings (e.g. "moo"). See "perlop: Equality Operators" for more complete details.

      — Ken

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (4)
As of 2023-12-03 20:37 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    What's your preferred 'use VERSION' for new CPAN modules in 2023?











    Results (20 votes). Check out past polls.

    Notices?