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

if (my $x = ...) { ... } elsif (my $x = ...) { ... }

ERROR:
"my" variable $x masks earlier declaration in same scope...

really? when does $x have scope outside the curlys?
or are the two blocks part of the same "virtual" code block?

Replies are listed 'Best First'.
Re: strange scope
by Zaxo (Archbishop) on May 22, 2007 at 23:29 UTC

    The scope of $x is the entire if . . . else construct. Since the condition following if is always evaluated, that is where the lone my declaration belongs.

    Consider,

    $ perl -Mstrict -e'if (my $x = 0) {} print $x,$/' Global symbol "$x" requires explicit package name at -e line 1. Execution of -e aborted due to compilation errors. $
    but,
    $ perl -Mstrict -e'if (my $x = 0) {} elsif (1) { print $x,$/}' 0 $
    I agree that that scope is not what one expects.

    Update: Two more cases,

    $ perl -Mstrict -e'if ($\) {print $x,$/} elsif (my $x = 0) {} else { p +rint $x,$/}' Global symbol "$x" requires explicit package name at -e line 1. Execution of -e aborted due to compilation errors. $
    but,
    $ perl -Mstrict -e'if ($\) {} elsif (my $x = 0) {} else { print $x,$/} +' 0 $
    showing that $x may be declared in an elsif condition to narrow its scope, if not needed in previous clauses.

    After Compline,
    Zaxo

      I agree that that scope is not what one expects.

      ISTR that this is a "problem" Perl 6's new scoping rules take care of.

Re: strange scope
by nedals (Deacon) on May 23, 2007 at 00:46 UTC
    Consider...
    if (my $x = ...) { ... } elsif (my $x = ...) { ... }

    Looking at it this way, you will notice that both $x's are actually in the same block. Hence the error.
    There are no 'curlys' enclosing the seperate conditions.

    So use something like Herkum's solution.

      Hi anagramster,

      If you use strict and warnings (or at least warnings), you should get a warning like:

      Found = in conditional, should be == at scope_test.pl line 5.

      For example, in the following code:

      use strict; use warnings; while (my $y = 1) { $y = 0; print "hello (y = $y)\n"; }

      You will get the warning about "= in conditional".

      It's also interesting to note that this test program, which is essentially equivalent to:

      use strict; use warnings; my $y = 0; while ($y = 1) { $y = 0; print "hello (y = $y)\n"; }

      will never terminate, even though $y is set to zero each time, because it's set to 1 immediately before the condition of the while statement is evaluated.


      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

        I find it interesting that so many people jumped on the "you don't want to do an assignment in a conditional" bandwagon without even knowing what the right hand side of the assignment is. There are times when an assignment in a conditional is exactly what you want. Here is an example that I use almost daily with DBIx::Class...

        my $rs = $schema->resultset( 'Foo' ); while ( my $record = $rs->next ) { # do something with the record }

        The warning you mention only occurs if the right hand side of the assignment is a constant, and the OP didn't include what was in the RHS.


        We're not surrounded, we're in a target-rich environment!
        I find it a bit strange that you assume that someone who askes about the meaning of a warning does not "use warnings".

        I also find it strange that a node that completely ignores the question of the op gets such a high reputation.


        Search, Ask, Know something completly different
        will never terminate, even though $y is set to zero each time, because it's set to 1 immediately before the condition of the while statement is evaluated.

        Yep, one would want to use redo there, in that case.

Re: strange scope
by naikonta (Curate) on May 23, 2007 at 02:17 UTC
    First of all, it's a warning instead of an error (unless of course you trapped warnings and turned it into fatal error). There are some classification of warnings, but they are usually a strong indication that something bad is lurking in the code that would give you unexpected result.
    are the two blocks part of the same "virtual" code block?
    They are in the same scope as Zaxo explains. You can also ask Perl for more help by using diagnostics:
    #!/usr/bin/perl use strict; use warnings; use diagnostics; if (my $x = 1) { # } elsif (my $x = 2) { # }
    would say:
    "my" variable $x masks earlier declaration in same scope at test.pl li +ne 9 (#1) (W misc) A "my" or "our" variable has been redeclared in th +e current scope or statement, effectively eliminating all access to t +he previous instance. This is almost always a typographical error. +Note that the earlier variable will still exist until the end of the +scope or until all closure referents to it are destroyed. Found = in conditional, should be == at test.pl line 9 (#2) (W syntax) You said if ($foo = 123) when you meant if ($foo == 123) (or something like that).

    But there's something else. If that's what you really have in your code, you get another warning as liverpole said about Found = in conditional as you can see from the output above. Why? Simple assignment like $x = 'some_value' would always evaluate to true or false (depending the value of 'some_value'). However, if you assign from an expression such as calling a function, then it would be taken as you assign a variable and want to evaluate that variable at once. Consider this:

    #!/usr/bin/perl use strict; use warnings; use diagnostics; # set $x from get_num(), if it returns true value # then do something with $x if (my $x = get_num()) { print $x, "\n"; } else { print "empty\n"; } sub get_num { 3 }
    would print 3 without any warnings. I use this construct very often to firstly save a value from a function calling to proceed it further only if it's true. And I keep the variable from visible out of the scope since the variable would be irrelevant for the rest of the code.

    If you instead, want to test beyond true or false of a variable, you could say for example:

    if ( (my $x = get_num()) >= 3 ) { ... }
    but I think it's too much noise. I would instead write something similiar to Herkum's code:
    my $x = get_num(); # assumed to always returns a number if ($x < 0 ) { # negative numbers } elsif ($x < 3) { # less } else { # OK }
    So, basically, be clear about what you want to do and what result you expect.

    Open source softwares? Share and enjoy. Make profit from them if you can. Yet, share and enjoy!

Re: strange scope
by Herkum (Parson) on May 22, 2007 at 23:26 UTC

    By doing this,

     if (my $x = 1)

    You are testing if the variable assignment works which is the wrong way to approach this problem. Try this,

    my $x = 1; if ($x > 1) { } # Do something if greater than one elsif ($x < 1) { } # Do something else here
      It wouldn't be hard to imagine a situation where you want test the returns from a list of functions, attempted in sequence until one of them returns true, and then do some specific block of code depending on which one returned true. In other words, something like this could be called for:
      my @params = ...; # whatever sets the context... if ( my $x = function_1( @params )) { # do something with the return value from function_1 } elsif ( $x = function_2( @params )) { # do something with the return value from function_2 } # ... and so on.
      I don't know if this is the OP's situation, but it would motivate the kind of approach the OP is asking about. There might be other idioms for doing this sort of thing, but doing it this way seems reasonable.
Re: strange scope
by anagramster (Novice) on May 23, 2007 at 16:21 UTC

    Thanks for all the feedback folks.

    Zaxo addressed the issue concisely - thanks.

    For the other posts that ranted about the logic, I am a bit surprised - first, because it's a bit odd to change the logic of some code to fix a warning, and second, it ignores TMTOWTDI (although the trend in paper tomes on Perl seem to be straying from that mantra).

    Anyways, here's the actual code snippet (parsing S-Expr subset):

    my $text; if (($text) = $line =~ m!^\s*\((.*)\)[\r\n]+$!) { # keyed value my ($key, $val) = split( /\s+/, $text, 2 ); $val =~ s!(^[\'\"]|[\'\"]$)!!g; $info->{$key} = $val; } elsif (($text) = $line =~ m!^\s*\'(.*)\'[\r\n]+$!) { # quoted value $text =~ s!(^[\'\"]|[\'\"]$)!!g; return $text; } elsif (my ($key) = $line =~ m!^\s*\((\S+)[\r\n]+$!) { # sub-level push @{$info->{$key} ||= []}, $self->_parse_vm_info( $lines ); }

    And a solution to the warning was never very interesting to me - I had solved it by polluting the enclosing block (as above) and while I liked the single initial def by Zaxo, I chose not to use that because future edits could make the def inadvertently disappear for the 2nd block (a minor annoyance).

    Onward, McPerl

      first, because it's a bit odd to change the logic of some code to fix a warning
      I don't think that logic changing is solely for fixing the warnings. The warnings show up because you ask for it, you ask Perl to warn you if it thinks that you might do something you don't intent. Perl is not always right about this, but mostly is. And there's another way to supress the warning if you're sure about what you're doing without changing the logic. Even if the changing is solely for fixing the warning, I don't see it as odd at all. Most Perlers, at least those around here, don't like the warnings show up, and clutter the error log unintentionally (in the case of web applications).
      second, it ignores TMTOWTDI
      How the logic changing ignores TMTOWTDI? Changing the way something is done is clearly to show another way to do it.
      And a solution to the warning was never very interesting to me
      I think I understand your intent in your OP. You think the elsif is in the equal scope as if but Perl doesn't think so hence the warning. But you're interested in knowing why instead of how to get rid of the warnings. The thing is, if you try to understand what the warnings mean, you'll know how to solve the real problem, and get rid of the warnings as well. Hopefully.

      Open source softwares? Share and enjoy. Make profit from them if you can. Yet, share and enjoy!