Scope and Arrays

A

Andrew

If I start with the following code;

use strict;
use warnings;

my $message = "Hello";

displayPrint();

sub displayPrint {

print "The message is $message.\n";

}

When I run it, I get the following output;

The message is Hello.

I would like to loop through several messages, so I have changed the
code to;

my $message;
my @messages = ("Hello", "Goodbye");

foreach $message (@messages) {
displayPrint();
}

sub displayPrint {

print "My message is $message.\n";

}

Then I get the following error message from the print line;

"Use of uninitialized value in concatenation (.) or string"

Can someone explain this to me ? From my understanding (obviously
wrong), $message is 'declared' at the start of the code so it should
be available for use within displayPrint. If I put an extra print
statement inside the foreach loop but before the branch to
displayPrint, the right value is displayed. Why isnt the value of
$message pass into displayPrint ?

Thanks for any help you can give.
 
A

anno4000

Andrew said:
If I start with the following code;

use strict;
use warnings;

my $message = "Hello";

displayPrint();

sub displayPrint {

print "The message is $message.\n";

}

When I run it, I get the following output;

The message is Hello.

I would like to loop through several messages, so I have changed the
code to;

my $message;
my @messages = ("Hello", "Goodbye");

foreach $message (@messages) {
displayPrint();
}

sub displayPrint {

print "My message is $message.\n";

}

Then I get the following error message from the print line;

"Use of uninitialized value in concatenation (.) or string"

Can someone explain this to me ? From my understanding (obviously
wrong), $message is 'declared' at the start of the code so it should
be available for use within displayPrint. If I put an extra print
statement inside the foreach loop but before the branch to
displayPrint, the right value is displayed. Why isnt the value of
$message pass into displayPrint ?

The variable in "foreach" is implicitly localized, as "perldoc perlsyn"
puts it. That means, the values from the list are only visible inside
the loop. Your sub "displayPrint" was compiled outside that scope,
so it doesn't get to see the values.

The actual problem is that your sub relies on a global variable
(the file-globa lexical $message) for its parameter. Unless there
is a compelling reason, the better practice is to pass the parameter
whith the sub call. What you are seeing is one of the problems
that can be avoided that way. Rewrite "displayPrint" as

sub displayPrint {
my $message = shift;
print "My message is $message.\n";
}

and call it like this

foreach my $messsage ( @messages ) {
displayPrint( $message);
}

or just

displayPrint( $_) for @messages;

Code untested, but that should work as planned.

Anno
 
A

Andrew

The variable in "foreach" is implicitly localized, as "perldoc perlsyn"
puts it. That means, the values from the list are only visible inside
the loop. Your sub "displayPrint" was compiled outside that scope,
so it doesn't get to see the values.

The actual problem is that your sub relies on a global variable
(the file-globa lexical $message) for its parameter. Unless there
is a compelling reason, the better practice is to pass the parameter
whith the sub call. What you are seeing is one of the problems
that can be avoided that way. Rewrite "displayPrint" as

sub displayPrint {
my $message = shift;
print "My message is $message.\n";
}

and call it like this

foreach my $messsage ( @messages ) {
displayPrint( $message);
}

or just

displayPrint( $_) for @messages;

Code untested, but that should work as planned.

Anno,

Thanks for the answer. My challenge is that the code I am changing is
not as simple as my example. It has 10 subs within the loop and some
have variables pass in already. For example;

foreach $message (@messages) {
callSub1;
callSub2;
callSub3("param1");
callSub4;
...
}

Now I understand the reason for the code behaving in that way, I can
start to look at the solution.

Regards,

Andrew
 
M

Mirco Wahab

Andrew said:
Thanks for the answer. My challenge is that the code I am changing is
not as simple as my example. It has 10 subs within the loop and some
have variables pass in already. For example;

foreach $message (@messages) {
callSub1;
callSub2;
callSub3("param1");
callSub4;
...
}

Now I understand the reason for the code behaving in that way, I can
start to look at the solution.

You could make a very simple change then:



my $message; # if 'inter package' calls => our $message
my @messages = ('Hello', 'Goodbye');

foreach my $actual (@messages) {
$message = $actual; # <== here
callSub1();
callSub2();
callSub3('param1');
callSub4();
}

sub callSub1 { print "1: $message. - @_\n" }
sub callSub2 { print "2: $message. - @_\n" }
sub callSub3 { print "3: $message. - @_\n" }
sub callSub4 { print "4: $message. - @_\n" }



Regards

M.
 
A

anno4000

Andrew said:
Anno,

Thanks for the answer. My challenge is that the code I am changing is
not as simple as my example. It has 10 subs within the loop and some
have variables pass in already. For example;

foreach $message (@messages) {
callSub1;
callSub2;
callSub3("param1");
callSub4;
...
}

Now I understand the reason for the code behaving in that way, I can
start to look at the solution.

Bad design then. You should refactor the subs to take parameters if
at all possible.

As an intermediate step, you could add a single argument (a hashref)
to all subs, where the hash holds the values of the (former) global
variables. That should be a straightforward process. You'd have a
single global hash, say %param, and then the calls become

foreach $message (@messages) {
callSub1( \ %param);
callSub2( \ %param);
callSub3("param1", \ %param);
callSub4( \ %param);
...
}

Of course, all subs must be adapted, but that would be the same
rather mechanical process for all of them. No fun, but doable
and probably worth it.

Anno
 
A

Andrew

You could make a very simple change then:

my $message; # if 'inter package' calls => our $message
my @messages = ('Hello', 'Goodbye');

foreach my $actual (@messages) {
$message = $actual; # <== here
callSub1();
callSub2();
callSub3('param1');
callSub4();
}

sub callSub1 { print "1: $message. - @_\n" }
sub callSub2 { print "2: $message. - @_\n" }
sub callSub3 { print "3: $message. - @_\n" }
sub callSub4 { print "4: $message. - @_\n" }

Regards

M.

Thanks for the reply. That was exactly the solution I came up with
myself after reading Annos post.

Thanks for all the help.

Andrew
 
P

Peter J. Holzer

Andrew said:
my $message;
my @messages = ("Hello", "Goodbye");

foreach $message (@messages) {
displayPrint();
}

sub displayPrint {

print "My message is $message.\n";

}

Then I get the following error message from the print line;

"Use of uninitialized value in concatenation (.) or string"

Can someone explain this to me ?
[...]

foreach() localises the iterator variable. Your loop is equivalent
to the following:

foreach (@messages ) { # implicit $_ loop iterator
local $message = $_;
displayPrint();
}

The localised $message is declared in the loop block's scope, which
isn't visible from the sub that is declared in a different scope.

Actually, "local"ized variables are visible in subs which are called
within their scope, but that works only with package-global variables,
not with lexicals. So one (rather ugly, I admit) solution would be to
change "my $message;" into "our $message;":

our $message;
my @messages = ("Hello", "Goodbye");

foreach $message (@messages) {
displayPrint();
}

sub displayPrint {

print "My message is $message.\n";

}

prints

My message is Hello.
My message is Goodbye.

I like Anno's way with the hash better, though (that's how I would do it).

hp
 
B

Brian McCauley

So one (rather ugly, I admit) solution would be to
change "my $message;" into "our $message;":

our $message;
my @messages = ("Hello", "Goodbye");

foreach $message (@messages) {
displayPrint();
}

sub displayPrint {

print "My message is $message.\n";

}

prints

My message is Hello.
My message is Goodbye.

I like Anno's way with the hash better, though (that's how I
would do it).

I think that the global variables design pattern should not be
dismissed out of hand. If used widely they will make code
unmaintainable and unreadable. But used sparingly they can make for
more readable and more maintainable.

To see my epiphany:

http://groups.google.com/group/comp...27ea3f9ab6f/2fbfe18a8d5e7acd#2fbfe18a8d5e7acd
 
A

anno4000

Brian McCauley said:
I think that the global variables design pattern should not be
dismissed out of hand. If used widely they will make code
unmaintainable and unreadable. But used sparingly they can make for
more readable and more maintainable.

To see my epiphany:

http://groups.google.com/group/comp...27ea3f9ab6f/2fbfe18a8d5e7acd#2fbfe18a8d5e7acd

I haven't followed your example in detail but I've seen similar cases.
I think it's no coincidence that your problem was recursive. Some
recursive knots can be solved by introduction of a global variable or
two. The sword of globality, as it were.

However, the code the OP has to deal with seems to be based on the
Design Pattern of "Make Everything Global and Access at Will". (MEGAW?)
That only works for small, one-off scripts. The problem is that small
one-off scripts get re-used and extended in infinitesimal steps way
beyond the breaking point.

Anno
 
U

Uri Guttman

a> I haven't followed your example in detail but I've seen similar cases.
a> I think it's no coincidence that your problem was recursive. Some
a> recursive knots can be solved by introduction of a global variable or
a> two. The sword of globality, as it were.

i haven't looked at the epiphany yet (may do it later) but you can still
use lexicals in such cases if they are file or block scoped with the
recursive code. or in some cases closures can solve tricky problems. of
course i am assuming globals are proper symbol table vars and that you
technically (almost? :) never need them.

a> However, the code the OP has to deal with seems to be based on the
a> Design Pattern of "Make Everything Global and Access at Will". (MEGAW?)
a> That only works for small, one-off scripts. The problem is that small
a> one-off scripts get re-used and extended in infinitesimal steps way
a> beyond the breaking point.

call it fortran common block coding! :)

uri
 
A

anno4000

Uri Guttman said:
a> However, the code the OP has to deal with seems to be based on the
a> Design Pattern of "Make Everything Global and Access at Will". (MEGAW?)
a> That only works for small, one-off scripts. The problem is that small
a> one-off scripts get re-used and extended in infinitesimal steps way
a> beyond the breaking point.

call it fortran common block coding! :)

I'll raise you to BCPL's global vector.

Anno
 
M

Martijn Lievaart

I'll raise you to BCPL's global vector.

Luckily it's been long enough or I would call on COBOLs common blocks. Or
whatever they were called.

Yuck.

M4
 

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

No members online now.

Forum statistics

Threads
474,202
Messages
2,571,057
Members
47,665
Latest member
salkete

Latest Threads

Top