Variables scope. Am lost...

C

chacallot

Hi!

I though I understood the thing : when using a my $variable the scope is
the {} block.

I learned that the hard way while doing my first perl scripts and I
thought I understood it all thanks to perlintro "variable scoping"
(http://aspn.activestate.com/ASPN/docs/ActivePerl/5.8/lib/Pod/perlintro.html)
:
"The variables are scoped to the block (i.e. a bunch of statements
surrounded by curly-braces) in which they are defined."



So can someone give me a hint or a link to the rite (f) manual about how
to understand the result of the following example.


----------------
use strict;


for (my $i=1;$i<3;$i++)
{

my $toto=$i;
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi
{
$toto++;
print "in sub toto=$toto\n";
}

}
---------------

The result from this script is :
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=2
in sub toto=3
after sub toto=2


Why is it that the first time, the $toto++ inside the sub affects the
value outside the sub and not the second time?


Rgds,
Chacallot.
 
P

Paul Lalli

chacallot said:
I though I understood the thing : when using a my $variable the scope is
the {} block.

I learned that the hard way while doing my first perl scripts and I
thought I understood it all thanks to perlintro "variable scoping"
(http://aspn.activestate.com/ASPN/docs/ActivePerl/5.8/lib/Pod/perlintro.html)
:
"The variables are scoped to the block (i.e. a bunch of statements
surrounded by curly-braces) in which they are defined."



So can someone give me a hint or a link to the rite (f) manual about how
to understand the result of the following example.


----------------
use strict;


for (my $i=1;$i<3;$i++)
{

my $toto=$i;
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi
{
$toto++;
print "in sub toto=$toto\n";
}

}
---------------

The result from this script is :
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=2
in sub toto=3
after sub toto=2


Why is it that the first time, the $toto++ inside the sub affects the
value outside the sub and not the second time?

Let's make a couple minor changes to your subroutine, and see if you
can figure it out from these results:

#!/usr/bin/env perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) { #two more loop iterations
my $toto=1; #to a constant, 1, not dependent on $i
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi {
$toto++;
print "in sub toto=$toto\n";

}
}
__END__
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=3
after sub toto=1
before sub toto=1
in sub toto=4
after sub toto=1
before sub toto=1
in sub toto=5
after sub toto=1

Does that give you a hint?

The problem is that subroutines are compiled exactly once. In this
case, the subroutine was compiled in the scope of the *first* iteration
of the loop. The $toto that it has access to is therefore the $toto
that is declared in the first iteration of the loop. After that
iteration has completed, subsequent iterations declare completely new
and unrelated variables, also named $toto. But the subroutine has
already been defined. It doesn't gain access to those variables. It
only has access to the original variable. So every time that
subroutine is called, it's accessing, incrementing, and printing the
$toto that the first loop iteration declared. When the program flow
goes back outside the subroutine, the scope of the new variable is
back, and the new variable (which we've now set to 1) is accessed.

Does that make sense? If not, let's look at one more slightly modified
example:

#!/usr/bin/env perl
use strict;
use warnings;

{
my $toto;
#now we declare the variable in the scope of both the subroutine
#and the *entire* for loop, not just one iteration of it

for (my $i=1;$i<5;$i++) {

$toto = 1;
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi {
$toto++;
print "in sub toto=$toto\n";

}
}
}
__END__

before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2

This time, both the subroutine and every iteration of the for loop are
accessing the same variable.

Hope this helps,
Paul Lalli
 
C

chacallot

Paul said:
Let's make a couple minor changes to your subroutine, and see if you
can figure it out from these results:

#!/usr/bin/env perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) { #two more loop iterations
my $toto=1; #to a constant, 1, not dependent on $i
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi {
$toto++;
print "in sub toto=$toto\n";

}
}
__END__
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=3
after sub toto=1
before sub toto=1
in sub toto=4
after sub toto=1
before sub toto=1
in sub toto=5
after sub toto=1

Does that give you a hint?

The problem is that subroutines are compiled exactly once. In this
case, the subroutine was compiled in the scope of the *first* iteration
of the loop. The $toto that it has access to is therefore the $toto
that is declared in the first iteration of the loop. After that
iteration has completed, subsequent iterations declare completely new
and unrelated variables, also named $toto. But the subroutine has
already been defined. It doesn't gain access to those variables. It
only has access to the original variable. So every time that
subroutine is called, it's accessing, incrementing, and printing the
$toto that the first loop iteration declared. When the program flow
goes back outside the subroutine, the scope of the new variable is
back, and the new variable (which we've now set to 1) is accessed.

Does that make sense? If not, let's look at one more slightly modified
example:

#!/usr/bin/env perl
use strict;
use warnings;

{
my $toto;
#now we declare the variable in the scope of both the subroutine
#and the *entire* for loop, not just one iteration of it

for (my $i=1;$i<5;$i++) {

$toto = 1;
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi {
$toto++;
print "in sub toto=$toto\n";

}
}
}
__END__

before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2

This time, both the subroutine and every iteration of the for loop are
accessing the same variable.

Hope this helps,
Paul Lalli

Rite, it works the way you say.

But in my mind, a sub is something which is re-initialized each time it
is instantiated (maybe I got this from C.. But maybe I dont know enough
about C either..) and I dont see any practical use as a dev of
"remembering" variables from the first instantiation. But pbly it is a
more efficient and simple way to implement subs.. Or maybe I should opt
for a more strongly typed/scoped language with a simple dumb logic that
would work everytime and is easy to understand.

Thanks for this fast and accurate answer,
Chacallot.
 
C

chacallot

chacallot said:
Rite, it works the way you say.

But in my mind, a sub is something which is re-initialized each time it
is instantiated (maybe I got this from C.. But maybe I dont know enough
about C either..) and I dont see any practical use as a dev of
"remembering" variables from the first instantiation. But pbly it is a
more efficient and simple way to implement subs.. Or maybe I should opt
for a more strongly typed/scoped language with a simple dumb logic that
would work everytime and is easy to understand.

Thanks for this fast and accurate answer,
Chacallot.

Or I should get a warning when I use strict and warning.

Looks screwed to me. First time, the sub inherit, but not on subsequent
calls. Looks really weird to me.

Should use strict and Warnings say anything about that?

Rgds,
Chacallot.
 
P

Paul Lalli

chacallot said:
Rite, it works the way you say.

But in my mind, a sub is something which is re-initialized each time it
is instantiated

That's simply not how a subroutine declaration works in Perl
(maybe I got this from C.. But maybe I dont know enough
about C either..)

I have no idea how it works in C.
and I dont see any practical use as a dev of
"remembering" variables from the first instantiation. But pbly it is a
more efficient and simple way to implement subs.. Or maybe I should opt
for a more strongly typed/scoped language with a simple dumb logic that
would work everytime and is easy to understand.

It's perfectly easy to understand, and works just fine. It just
doesn't do what *you* wanted it to do in this particular instance.

For what it's worth, if you want the behavior you were apparently
expecting, you could instead declare an anonymous subroutine reference
each time through the loop. This declaration *would* occur for each
iteration of the loop, rather than only once:

#!/usr/bin/env perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
my $toto = 1;
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}
__END__
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2

Paul Lalli
 
P

Paul Lalli

chacallot said:
Or I should get a warning when I use strict and warning.

Honestly, I thought it would have to, and was about to chide you for
not including 'use warnings' in your original post. I was surprised
that no warning was emitted when I added it in.
Looks screwed to me. First time, the sub inherit, but not on subsequent
calls. Looks really weird to me.

I don't know what you mean by "inherit." Subroutines are defined once
and only once, regardless of their location in code. Subroutines have
access to lexical variables that are in the same scope as when they are
defined. Which of those two properties do you take issue with?
Because the results (that a subroutine declared within a loop body that
also declares a lexical variable has access only to the first
iteration's variable) pretty clearly follows from those two properties.
Should use strict and Warnings say anything about that?

"Should" it? <shrug> That's a matter of personal preference, I'd
guess. If you think it's worth it, use `perlbug` to submit a feature
request.

Paul Lalli
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
chacallot
Hi!

I though I understood the thing : when using a my $variable the scope is
the {} block.

.... and there are some gotchas, like in some blocks with "preable"
(e.g., if/foreach, etc) the preamble is coopted into the block, in
some others it is not.
So can someone give me a hint or a link to the rite (f) manual about how
to understand the result of the following example.

The behaviour of this chunk is not defined, so you will not find it in
manuals. The simple rule-of-thumb is that you do not want to define
named subroutines inside other subroutines. Here you got something
yet more bizarre: you do not have an enclosing subroutine, but you
still get problems with lexicals.

[Actually, I would probably classify what you see as a bug, but I
have no idea how one could fix it... I would expect the same will
appear if you would used a closure instead of a named subroutine...]

[I reformatted your code; it is has wrong indentation, and the
curly-style is not suitable for Usenet postings]
use strict;
for (my $i=1;$i<3;$i++) {
my $toto=$i;
print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi {
$toto++;
print "in sub toto=$toto\n";
}
}
The result from this script is :
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=2
in sub toto=3
after sub toto=2

As others explained, this is related to the dynamic nature of
lexicals (kind of auto-vivication); try this:

use strict;
for (my $i=1;$i<5;$i++) {
my $toto=$i;
print \$toto, "\n";
titi();

sub titi {
$toto++;
}
}

You see that on the first iteration $toto is one variable, on the
others it is different. The reason is that:

a) on the first iteration, the slot of $toto is "empty"; so it is
initialized to become a scalar with the same value as $i;

b) on the second iteration, the slot is non-empty, so "my operator"
is checking whether it can reuse the old value; it discovers
that this value is a large refcount (since it is referenced from
sub titi). This means that the old value cannot be reused (it
could become a value of some "complicated data structure"
created during preceeding operation); a new variable
("container") is created, and populated by the value of $i;

c) on consequent iterations, the same check shows that the
container created on the step "b" is not used anywhere else; so
it is reused.

However, sub titi() has no knowledge that name $toto refers to
different variables; so it uses the "default" value, which coincides
with the first value assigned on the step "a".

Hope this helps,
Ilya
 
D

Dr.Ruud

chacallot schreef:
So can someone give me a hint or a link to the rite (f) manual about
how to understand the result of the following example.

Let's first clean up a bit:
for my $i (1..2)
{
my $toto = $i;

print "before sub toto=$toto\n";
titi();
print "after sub toto=$toto\n";

sub titi
{
$toto++;
print "in sub toto=$toto\n";
}
}


Your titi() is inside your for-loop. You have already been explained
what the consequences are of that construct.


Regarding the output that you expect, this is more like what you need:

#!/usr/bin/perl
use strict ;
use warnings ;

sub titi
{
for (@_)
{
$_ ++ ;
print "in sub: $_\n" ;
}
}

for ( my $toto = 1 ; $toto < 5 ; $toto ++ )
{
print "1-before sub toto=$toto\n" ;
titi $toto, $toto, $toto, $toto, $toto, $toto ;
print "1-after sub toto=$toto\n\n" ;
}

for my $toto ( 1 .. 5 )
{
print "2-before sub toto=$toto\n" ;
titi $toto, $toto, $toto, $toto, $toto, $toto ;
print "2-after sub toto=$toto\n\n" ;
}
 
L

Lukas Mai

Paul Lalli said:
That's simply not how a subroutine declaration works in Perl


I have no idea how it works in C.

C doesn't "reinitialize" functions. In fact, C doesn't allow nested
functions (i.e. no function has access to an outer lexical environment).

There's a gcc extension for local functions, but it works more like
local *foo = sub {...}; in Perl.

Lukas
 
B

Brad Baxter

Paul said:
For what it's worth, if you want the behavior you were apparently
expecting, you could instead declare an anonymous subroutine reference
each time through the loop. This declaration *would* occur for each
iteration of the loop, rather than only once:

#!/usr/bin/env perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
my $toto = 1;
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}
__END__
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
Odd:
#!/usr/local/bin/perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
my $toto = 1;
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}
Global symbol "$toto" requires explicit package name at ./qt line 7.
Global symbol "$toto" requires explicit package name at ./qt line 8.
Execution of ./qt aborted due to compilation errors.

Perhaps you meant:
#!/usr/local/bin/perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $toto = 1;
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
before sub toto=1
in sub toto=2
after sub toto=2
 
P

Paul Lalli

Brad said:
Odd:

#!/usr/local/bin/perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
my $toto = 1;
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}

Global symbol "$toto" requires explicit package name at ./qt line 7.
Global symbol "$toto" requires explicit package name at ./qt line 8.
Execution of ./qt aborted due to compilation errors.

Sigh. I broke the cardinal rule of posting code to a technical
newsgroup - NEVER change the code after you've run it without running
it again. My profuse apologies.
Perhaps you meant:

#!/usr/local/bin/perl
use strict;
use warnings;

for (my $i=1;$i<5;$i++) {
my $toto = 1;
my $titi = sub {
$toto++;
print "in sub toto=$toto\n";
};
print "before sub toto=$toto\n";
$titi->();
print "after sub toto=$toto\n";
}

Indeed I did. Thank you very much for the corrections.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,225
Members
46,815
Latest member
treekmostly22

Latest Threads

Top