Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Declaring with my, assigning, and testing in same line

by mordibity (Acolyte)
on Dec 11, 2015 at 14:41 UTC ( [id://1150034]=perlquestion: print w/replies, xml ) Need Help??

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

Simplest I think is to just post the canonical example that I run across again and again... I would dearly like this to be all on one line for concision and clarity:

(my @v = keys %foo) == 1 or die "Got more than one: @v\n"; print "V:@v\n";
This gives:
Possible unintended interpolation of @v in string... Global symbol "@v" requires explicit package name...

It's the scoping of the parentheses around the my assignment that causes the problem. If I leave out @v from the error message, the logic is correct and everything works: @v, if assigned to successfully, is correctly set and visible for the print statement. I'm trying to avoid doing work twice (repeating the call to keys in the error message).

The ugly alternative is to move my @v outside the comparison, but it just looks clunky and messes up a nice column of assignments. It's not just hash checking, it could be anything:

my $x = parse_thing(); my $y = $x->{item} or die "Item not found in x.\n"; my @z; (@z = do_xyz()) < 3 or die "Got extra Z: @z\n"; print "Z:@z\n";

This has been bugging me for years, and I'm resigned to it, just wondering if there was a clever way around it.

Thanks!

Replies are listed 'Best First'.
Re: Declaring with my, assigning, and testing in same line (scope)
by tye (Sage) on Dec 11, 2015 at 14:56 UTC
    It's the scoping of the parentheses around the my assignment that causes the problem.

    The parens have nothing to do with it. A 'my' declaration has no effect until the statement after the 'my'. This is, at least in part, so you can do:

    my $x = 2; { my $x = 3 * $x; # $x == 6 } # $x == 2

    As to your question, you can get away with:

    (@$_ = keys %foo) == 1 or die "Got more than one: @$_\n" for \my @v;

    But I'm not sure it is worth it.

    - tye        

Re: Declaring with my, assigning, and testing in same line
by NetWallah (Canon) on Dec 11, 2015 at 15:27 UTC
    Slightly more localized variables than tye, but still hardly worth it:
    my @v=sub{@_>1 and die "Got more than one: @_\n";@_}->(keys %x)'

            Our business is run on trust. We trust you will pay in advance.

Re: Declaring with my, assigning, and testing in same line (custom routine)
by LanX (Saint) on Dec 12, 2015 at 03:17 UTC
    First this behaviour is well documented in perlsub ¹

    And if it's not a frequent use case, please use two lines.

    The next maintainer will be thankful to easily read how @v is defined:

    my @v= keys %h; die "Got extra Z: @v\n" if @v >1

    Otherwise if you really need it that often, why don't you simply define a helper function?

    DB<100> sub check { die "Got extra Z: @_\n" if @_ >1 } DB<101> %h=(a=>1) => ("a", 1) DB<102> check my @v= keys %h => "" DB<103> %h=(a=>1,b=>2) => ("a", 1, "b", 2) DB<104> check my @v= keys %h Got extra Z: a b

    And if you need more flexibility in testing, use some functional magic

    DB<106> sub avoid (&;@) { my $code=shift; my $msg = $code->(@_); die $msg if $msg; } DB<107> avoid { "Got extra Z: @_\n" if @_ >1 } my @v = keys %h; Got extra Z: a b DB<108> %h=(a=>1) => ("a", 1) DB<109> avoid { "Got extra Z: @_\n" if @_ >1 } my @v = keys %h; DB<110>

    but again, for the sake of readability please use a line break (even if it's a one liner)

    avoid { "Got extra Z: @_\n" if @_ >1 } my @v = keys %h;

    And for completeness , I'm sure you could also use variable :attributes for such checks.

     my @v :check = keys %h;

    See Attribute::Handlers for details.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

    ¹)

    The declared variable is not introduced (is not visible) until +after the current statement. Thus, my $x = $x; can be used to initialize a new $x with the value of the old $x +, and the expression my $x = 123 and $x == 123 is false unless the old $x happened to have the value 123.
      And for completeness , I'm sure you could also use variable :attributes for such checks.
      It's possible, but not straightforward. Attributes alone can't do it, because they don't happen at runtime, so they don't see the assigned values:
      my @v :Nonempty = keys %h;

      Nonempty has no acces to the keys of %h. Fortunately, you can use attributes with tie. It's still not easy, though: the constructor (TIEARRAY) doesn't see the assigned values, either. STORE sees them, but it's not called when the right hand side list is empty! Fortunately, there's one method that gets called in any assignment: EXTEND:

      #!/usr/bin/perl use warnings; use strict; { package Array::Nonempty::Attr; use Attribute::Handlers; sub Nonempty :ATTR(ARRAY) { tie @{ $_[2] }, 'Array::Nonempty' } } { package Array::Nonempty; use Tie::Array; use parent -norequire => 'Tie::StdArray'; use Carp; sub EXTEND { my ($self, $size) = @_; croak "Cannot be empty" if 0 == @$self && 0 == $size; $self->SUPER::EXTEND($size) } } use parent -norequire => 'Array::Nonempty::Attr'; my %hash_ok = ( answer => 42 ); my %hash_empty = (); my @keys_ok :Nonempty = keys %hash_ok; my @keys_empty :Nonempty = keys %hash_empty;

      You can add other methods to check they wouldn't help you:

      To create an array that can never be empty, you'd have to implement SHIFT, POP, and SPLICE, too.

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
        Thanks I started experimenting myself after you messaged me.

        As said you'll need to tie cause Attributes happen at compile time and you have to intercept the assignment in STORE.

        (didn't know about EXTEND till now)

        I'm fine with using tie as long as I can untie when I'm done. Otherwise the penalty for this syntactic sugar would be to huge.

        I took a simpler case, tieing a scalar, and trying to use untie $$self within STORE {}. Didn't work. (probably because dereferencing isn't allowed for untie or scope restrictions)

        IMO as long we can't automatically untie, this approach shouldn't be used.

        update

        seems liked I didn't realy understood Tie::StdScalar yet

        update
        this approach with autotieref

        use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };

        should provide the necessary reference to the tied structure, so that untieing becomes possible.

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

Re: Declaring with my, assigning, and testing in same line
by ww (Archbishop) on Dec 11, 2015 at 17:46 UTC

    Seems to me the issue is the way doublequotes are interpolated. So...
          mayybe simpler still, but -- admittedly -- not one line:

    #!/usr/bin/perl -w use 5.018; use strict; # 1150034 my %foo = ("do"=> "done", "eggs" =>"green ham", "was" => "were",); my @v; (@v = keys %foo) == 1 or warn "Got more than one: " . @v . "\n"; print "V:@v\n";

    OUTPUT:

    Got more than one: 3 V:was do eggs
      ... the issue is the way doublequotes are interpolated.

      No, per tye above, the problem is with scoping within a statement:

      c:\@Work\Perl\monks>perl -wMstrict -le "my %foo = qw(do done eggs ham was were); ;; my @v; (@v = keys %foo) == 1 or warn qq{Got more than one: @v}; print qq{V:@v}; " Got more than one: eggs was do at -e line 1. V:eggs was do


      Give a man a fish:  <%-{-{-{-<

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (5)
As of 2024-04-23 23:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found