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

Hello Monks,

I wrote a primitive parser of a simple configuration file, and I wrote some Perl that generates warnings. In my head, the two variable definitions are in different scopes, but the interpreter doesn't think the same way I do. It seemed to me that, since the if and elsif clauses can never be executed simultaneously, the my declarations are in different lexical scopes. Also, once we leave the if/elsif/else blocks, the temporary variables are out of scope, so there could never be confusion as to whether they were defined in the if or the elsif.

Can someone explain to me why it works this way?

#!/usr/bin/perl use warnings; use strict; my (%foos, %bars); while( my $line = <DATA> ) { chomp $line; if ( my ( $key, $val ) = $line =~ /^foo ([^:]*):([^:]*)$/ ) { push( @{ $foos{ $key } }, $val ); } elsif ( my ( $key, $val ) = $line =~ /^bar ([^:]*):([^:]*)$/ ) { push( @{ $bars{ $key } }, $val ); } else { # warn qq( "$line" is the wrong format.\n ); } } __DATA__ foo key1:val1 bar key2:val2 foo key3:val3 bar key4:val4 3 bad:val
"my" variable $key masks earlier declaration in same scope at ./test.pl line 11.
"my" variable $val masks earlier declaration in same scope at ./test.pl line 11.

Replies are listed 'Best First'.
Re: lexical scope in if/elsif tests
by CountZero (Bishop) on Apr 05, 2010 at 13:41 UTC
    You will have to drop the my statement in the elsif.

    From the docs in perlsub:

    The my operator declares the listed variables to be lexically confined to the enclosing block, conditional (if/unless/elsif/else), loop (for/foreach/while/until/continue), subroutine, eval, or do/require/use'd file.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      I ended up using a second variable name for the elsif test. Thanks for pointing me to the right place in the documentation.
Re: lexical scope in if/elsif tests
by BrowserUk (Patriarch) on Apr 05, 2010 at 13:58 UTC

    IMO it is cleaner to use $1 & $2:

    my (%foos, %bars); while( my $line = <DATA> ) { chomp $line; if ( $line =~ /^foo ([^:]*):([^:]*)$/ ) { push( @{ $foos{ $1 } }, $2 ); } elsif ( $line =~ /^bar ([^:]*):([^:]*)$/ ) { push( @{ $bars{ $1 } }, $2 ); } else { # warn qq( "$line" is the wrong format.\n ); } }

    Or named captures if you're using 5.10:

    my (%foos, %bars); while( my $line = <DATA> ) { chomp $line; if ( line =~ /^foo (?<KEY>[^:]*):(?<VAL>[^:]*)$/ ) { push( @{ $foos{ $+{KEY} } }, $+{VAL} ); } elsif ( $line =~ /^bar (?<KEY>[^:]*):(?<VAL>[^:]*)$/ ) { push( @{ $bars{ $+{KEY} } }, $+{VAL} ); } else { # warn qq( "$line" is the wrong format.\n ); } }

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: lexical scope in if/elsif tests
by NetWallah (Canon) on Apr 05, 2010 at 19:26 UTC
    Here is my offering of code that avoids the issue and
    • Allows for easy expansion of conditions
    • Avoids repeating the RegEx
    • Simplifies logic
    • Detects and identifies Separates error conditions
    #!/usr/bin/perl use warnings; use strict; my (%foos, %bars); my %targethash =( foo => \%foos, bar => \%bars, ); while( my $line = <DATA> ) { chomp $line; my ($name, $key, $val ) = $line =~ /^(\w+) ([^:]*):([^:]*)$/ ; if (! $name ){ warn qq( "$line" is the wrong format.\n ); }elsif (my $target = $targethash{$name} ){ $target->{$key} = $val; } else { warn "Line: $. has Invalid name '$name'\n"; } } __DATA__ foo key1:val1 bar key2:val2 foo key3:val3 qux key6:val6 bar key4:val4 3 bad:val 4bad:syntax

         Syntactic sugar causes cancer of the semicolon.        --Alan Perlis

      Much appreciated. I would vote you up if I could. I can't use your code because the example I posted was contrived. The real code I'm using has keys and values flipped based on which hash I'm putting them in. I'm doing it that way because it makes the config file more readable and the flow of the script more logical.
        It is easy enough to generate a "correctly" (in terms of ease-of-programming) directed hash from "flipped" values from a config file:
        Untested pseudo-code:
        my %correct; while (<config-file>){ ::Read data in the form $dest_hash_name, $text, [other info...] $correct{$text} = Generate_hashref($dest_hash_name); }

             Syntactic sugar causes cancer of the semicolon.        --Alan Perlis

Re: lexical scope in if/elsif tests
by Anonymous Monk on Apr 05, 2010 at 13:37 UTC

    The mys are outside of the squiggly brackets of the if.

    That means they look like they're in the outer scope to me ;)

      Not so: the $key and $val lexical variables are scoped to the whole of the if ... elsif ... else ... block. You get an error if you use them outside of that block.

      CountZero

      A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

        Good to know; I'll have to shift my perspective a little bit to make it look right.