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