Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Variable scoping oddity inside (??{ ... })

by Roger (Parson)
on Oct 31, 2003 at 03:22 UTC ( #303483=perlquestion: print w/ replies, xml ) Need Help??
Roger has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks, I saw the question Regexp for alphabetical order match within the string this morning. So I decided to give it a try, and came up with the following solution with matching time interpolation (??{ ... }) inside the regular expression -

use strict; my $seq1 = 'aabcdefg'; my $seq2 = 'aabcdefgabcde'; sub in_sequence { my $str = shift; # fetch the string value my $ok = 1; # set private variable $ok = 1 $str =~ m/(.)(.)(??{ $ok = 0 if $2 lt $1; 0 })/; return $ok; } print "Sequence is ", in_sequence($seq1)?"good":"bad", "\n"; print "Sequence is ", in_sequence($seq2)?"good":"bad", "\n";
What I want to achieve in subroutine in_sequence is as follows:
  • get the string value;
  • assign value 1 to my variable $ok;
  • in the regular expression, I fetch two characters at a time, and set $ok to 0 if the 2nd character is less than the 1st character;
  • return the value of $ok to the caller.

    However I am not getting the expected result. The values returned by the subroutine for $str1 and $str2 are both true.

    So I expanded the regular expression and inserted a few print statements. The debug version of the code is as follows:
    use strict; my $seq1 = 'aabcdefg'; my $seq2 = 'aabcdefgabcde'; sub in_sequence_debug { my $str = shift; my $ok = 1; print "Value of OK is $ok\n"; $str =~ m/(.)(.)(??{ print "$1 - $2 "; if ($2 lt $1) { $ok = 0; print "Not Ok\n" } else { print "Ok\n"; }; 0 })/; print "Value of OK is $ok\n"; return $ok; } print "Sequence is ", in_sequence_debug($seq1)?"good":"bad", "\n"; print "-"x40, "\n"; print "Sequence is ", in_sequence_debug($seq2)?"good":"bad", "\n";
    When I ran the code, I got the following output:
    Value of OK is 1 a - a Ok a - b Ok b - c Ok c - d Ok d - e Ok e - f Ok f - g Ok Value of OK is 1 Sequence is good ---------------------------------------- Value of OK is 1 a - a Ok a - b Ok b - c Ok c - d Ok d - e Ok e - f Ok f - g Ok g - a Not Ok a - b Ok b - c Ok c - d Ok d - e Ok Value of OK is 1 Sequence is good
    It seems that the value of my $ok has not been modified by the code $ok = 0 inside (??{ ... }).

    This suddenly made me curious and created the following test code to investigate the scoping of the variable $ok -
    use strict; my $seq1 = 'aabcdefg'; my $seq2 = 'aabcdefgabcde'; my $ok; # moved up to the file scope sub in_sequence_debug { my $str = shift; $ok = 1; print "Value of OK is $ok\n"; $str =~ m/(.)(.)(??{ print "$1 - $2 "; if ($2 lt $1) { $ok = 0; print "Not Ok\n" } else { print "Ok\n"; }; 0 })/; print "Value of OK is $ok\n"; return $ok; } print "Sequence is ", in_sequence_debug($seq1)?"good":"bad", "\n"; print "-"x40, "\n"; print "Sequence is ", in_sequence_debug($seq2)?"good":"bad", "\n";
    And this time, the output becomes -
    Value of OK is 1 a - a Ok a - b Ok b - c Ok c - d Ok d - e Ok e - f Ok f - g Ok Value of OK is 1 Sequence is good ---------------------------------------- Value of OK is 1 a - a Ok a - b Ok b - c Ok c - d Ok d - e Ok e - f Ok f - g Ok g - a Not Ok a - b Ok b - c Ok c - d Ok d - e Ok Value of OK is 0 Sequence is bad
    It actually worked when I move the $ok variable up one level to the file scope!

    It seems that something odd is happing to the variable scoping inside the match-time interpolation (??{ ... }), the variable assignment wrapped inside has no effect on my variables inside the subroutine, probably creates its own private variable, even when I do not explicitly declare them with my.

    Can anyone please explain to me why, by moving my $ok up one level into file scope, made the assignment $ok = 0 inside (??{ ... }) to work all the sudden?

    Thanks in advance!

  • Comment on Variable scoping oddity inside (??{ ... })
    Select or Download Code
    Re: Variable scoping oddity inside (??{ ... })
    by diotalevi (Canon) on Oct 31, 2003 at 03:30 UTC
      (?{ ... }) captured the original $ok in a closure so when the code executed the second time the my() caused the returned $ok to be a different $ok than the one the (?{...}) block was changing. Consider writing to a global instead.
          bleadperl properly gives the 'Variable "$ok" will not stay shared' warning

        To be more precise, Perl actually used the same instance of closure in this case. If Perl creates a new instance of the closure every time it enters the regexp, then this behavior will disappear.

          Ok, yes. That's because the m(...)'s (?{...}) is part of the optree and isn't being renewed each time. Using qr() wouldn't help because that'd have the same problem except now the closure would be bound to the qr() object.

          An alternative idea interpolates a random value into a (?#...) comment node so every time the code executes the regex's string value is different and the regex is recompiled (of course, don't use /o on this).

          /...(?{ ... })(?#@{[ rand ]})/
    Re: Variable scoping oddity inside (??{ ... })
    by pg (Canon) on Oct 31, 2003 at 04:37 UTC

      This might help you visually observe what happened:

      use strict; my $seq1 = 'aabcdefg'; my $seq2 = 'aabcdefgabcde'; in_sequence($seq1); in_sequence($seq1); sub in_sequence { my $str = shift; # fetch the string value my $ok = 1; # set private variable $ok = 1 print "outside regexp, ", \$ok, "\n"; $str =~ m/(.)(.)(??{ print "inside regexp, ", \$ok, "\n"; 0 })/; return $ok; }
    Re: Variable scoping oddity inside (??{ ... })
    by sauoq (Abbot) on Oct 31, 2003 at 09:57 UTC

      Scoping issues with code evaluation asserstions? deals with the exact same problem. You can get around it with a global as diotalevi suggested, but somewhere in that thread (in a discussion with diotalevi no less) I suggested using a local alias to a lexical instead. For example, change

      my $ok = 1; # set private variable $ok = 1 $str =~ m/(.)(.)(??{ $ok = 0 if $2 lt $1; 0 })/;
      to
      my $ok = 1; local *myok = \$ok; # Needed to avoid closure in code assertion. $str =~ m/(.)(.)(??{ $myok = 0 if $2 lt $1; 0 })/;

      I still think that's the best way to handle it.

      P.S. As a matter of good style, it's best to avoid comments — like "set private variable $ok = 1" — that just restate the code without adding information. You really don't have to tell anyone what my $ok = 1; does. On the other hand, it may be helpful to explain what $ok is used for.

      -sauoq
      "My two cents aren't worth a dime.";
      

    Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Node Status?
    node history
    Node Type: perlquestion [id://303483]
    Approved by davido
    Front-paged by gmax
    help
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others imbibing at the Monastery: (12)
    As of 2014-09-19 20:15 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      How do you remember the number of days in each month?











      Results (145 votes), past polls