At 12:10 PM 2/8/2006, Glenn Linderman wrote:
On approximately 2/8/2006 6:40 AM, came the following characters from the keyboard of John Deighan:
I realize that most things that look like bugs in Perl are in reality programmer bugs, but I can't figure out why the second call to the function "parse()" in the code below fails..........
So, $lTokens is a reference from a sub, to a variable declared outside of the sub (specifically, to the $lTokens declared in parse on its first invocation), thus making gettoken a closure.... and causing that instance of $lTokens to be preserved as part of the state of the closure.

        return undef;
        }
    my $token = shift(@$lTokens);

During the first sequence of calls to gettoken, it modifies that saved value, eventually reducing it to an empty list. During the second call to parse, regardless of the fact that a new variable $lTokens has been instantiated by the second call, doesn't change the state of the closure gettoken, which has captured the first instance of $lTokens (and then modified that value).

I had quite a discussion about this sort of thing with Dave Mitchell, who seems to be the closure expert in p5p, on the p5p list some years back, when I first "discovered" closures. Read about closures in the Perl documentation for more enlightenment. I think I kind of understand them at this point, and am willing to discuss it further if reading the perl documentation about closures, and comparing it to what you have done in your example here, doesn't provide enlightenment to you. Perhaps you already understand closures, but didn't realize that perl provided them? I was in the state of having heard that perl provided them, but not really understanding what they were.

I think I understand, but let me summarize: The function gettoken() is created as a separate object, which contains a copy of the variable $lTokens in the enclosing function parse() at the point that parse() is first called. At that point, the variable $lTokens points to an array containing 3 items, but during the first running of parse(), those items are removed. When the second call to parse() occurs, the function gettoken() is not rebuilt, but still contains a reference to the original array, which is empty, and which the new instance of $lTokens does not refer to. So, in fact, gettoken() is not getting tokens from the list referred to by the variable $lTokens, but instead from the original array, which is now empty.

Part of my problem here stems from wanting functions to work like they do in Pascal, where you can create functions inside of functions, where the inside functions can manipulate the variables defined in their enclosing functions. To me, this is very intuitive and a very powerful abstraction mechanism. You can't do that in C, where all functions are top level. However, the syntax in Perl fooled me into thinking that the embedded functions work like they would in Pascal. I didn't realize that they were really closures, which I have worked with before, but usually by creating anonymous functions and returning them as the result of a function call. Now that I understand the situation, I can offer the following as a way to safely do what I was trying to do in the first place - by creating an anonymous function and assigning it to a variable named $gettoken, then calling it with the syntax &$gettoken(). This has the effect of forcing the closure (i.e. the anonymous function) to be re-built each time that parse() is called instead of just the first time. (Actually, this was suggested to me by Steve Hurst, though at that time I still didn't understand why this was any different than my original code).

Anyway, here's the code that works, and now I understand why the previous code didn't work. I've removed the unused parameter from the parse() function (it was left over from when I stripped out irrelevant code to this problem) and renamed the variable $lTokens in the parse() function to $list so as to not have 2 different variables with the same name, but other than that, the main change was to change the "sub gettoken {" to "my $gettoken = sub {" to force the rebuilding of the closure to occur each time that parse() is called.


use strict;

my $lTokens = [qw(2 * 2)];
my $errmsg = parse($lTokens);
print($errmsg ? "ERROR: $errmsg\n" : "OK\n");

$lTokens = [qw(2 * 2)];
my $errmsg = parse($lTokens);
print($errmsg ? "ERROR: $errmsg\n" : "OK\n");

# -------------------------------------------

sub parse { my($list) = @_;

        my $gettoken = sub {

        if (@$list==0) {
                return undef;
                }
        my $token = shift(@$list);
        return $token;
        }; # gettoken()

my $tok = &$gettoken();
print("TOKEN: '$tok'\n");
return("Empty Input") if !$tok;
$tok = &$gettoken();
while ($tok) {
        print("TOKEN: $tok\n");
        $tok = &$gettoken();
        }
}



_______________________________________________
Perl-Win32-Users mailing list
Perl-Win32-Users@listserv.ActiveState.com
To unsubscribe: http://listserv.ActiveState.com/mailman/mysubs

Reply via email to