The following code loops endlessly:
use strict; use warnings; use File::Spec::Functions "devnull"; sub bar { open my $fh, "< $_[0]" or die "couldn't open $_[0]: $!"; print while <$fh>; } sub foo { for (@_) { bar(devnull()) } } $. = 0; foo($.);
Do you see why? This is an especially bad variant on the common while-loop inside for smashing aliased $_ problem, which usually elicits a couple of points: But here, there's something else going on. A magic variable $. is being passed around; only its value is actually wanted, but instead, the container of the value is given. This can lead to trouble. Consider this case:
use strict; use warnings; use Data::Dumper; $Data::Dumper::Terse=1; sub foo { print "foo: bad args: ", Dumper \@_ if @_ != 2 || $_[0] !~ /^[tf]/ || $_[1] !~ /statement/; my ($tf, $statement) = @_; if ($tf =~ /^t/) { # Line 12 print "true: $statement\n"; } else { print "false: $statement\n" } } for my $statement ("the following statement is true", "the preceding statement is false") { $statement =~ /\b(t|f|true|false)\b/ or next; foo($1, $statement); }
Here, I am calling foo("true", "the following statement is true"); and foo("false", "the following statement is false");, but by the time foo gets to line 12, $tf is not set to the passed parameter. The container $1 was passed, and before it was assigned to $tf, an intervening regex undefined it. (However, because $1 is dynamically scoped, once foo() returns, $1 will again be "true" or "false".)

The moral here is to beware action at a distance; when passing magic variables, or even your own globals, if you want them passed by value, explicitly stringify or numify them:

foo(0+$.); # and foo("$1", $statement);
so that the callee gets the value at the time of the call.

Replies are listed 'Best First'.
Re: Passing globals and magic variables safely
by leriksen (Curate) on Apr 08, 2005 at 02:26 UTC
    Some comments on the "action at a distance" effect.

    I guess this is why I have become more and more a fan of truly functional programming, especially for the lack of side effects. I try very hard to avoid all "action at a distance" coding

    Explicitly I try to write code

    • consisting only of functions i.e. the main script is as small as possible, work is handed off to a function in a module as soon as possible. Typically my main script is limited to reading and checking constraints on command line options
    • I try really hard to not have a function refer to anything outside of its immediate scope.
    • I can only use a module's services via its functional interface i.e. I cannot do $Some::Module::variable = 'ugh'; from anywhere except inside Some::Module
    • I try to limit functions to using only the parameters they are passed. When absolutely required, I will access a variable in the modules scope, but outside the modules function e.g. I try to avoid
      Package Some::Module; ... my $some_state = 'something'; ... sub some_func { ... $some_state = 'new state'; ... }

      as much as possible.
    • For objects, I also try to apply these principles - except where really required, I prefer to use $self->some_accessor() than $self->{some_attribute}
    • When I do have to break these rules and effect an "action at a distance" , it is really clear that I have to. The reason may be bad design or bad implementation, but at some point I have to say "until I can think of a better way, lets mark this as a TODO, and keep coding". Once the job at hand is working, I try to come back to the one or two instances of "action at a distance" and try to find a way to remove them
    • I have found this approach has made my code much better structured, much less complex, more flexible and more reusable.

      ...it is better to be approximately right than precisely wrong. - Warren Buffet

Re: Passing globals and magic variables safely
by itub (Priest) on Apr 07, 2005 at 21:56 UTC
    Interesting example. I think I instinctively avoid passing magic variables around, so I hadn't seen this kind of problem before.
Re: Passing globals and magic variables safely
by ihb (Deacon) on Apr 10, 2005 at 14:01 UTC

    I avoid the $<DIGITS> issue by hardly ever using them, and when I do use them I only use them at right hand side of an assignment and immediately after the match. I.e. I do my ($foo, $bar) = $baz =~ /.../ and $baz =~ /.../; my ($foo, $bar) = ($1, $2);.

    ihb

    See perltoc if you don't know which perldoc to read!

      Me too. In production code, I almost never use the regex variables except in a s///. Note that it is pretty easy to combine this with checking for successful match:
      my ($foo, $bar); unless (($foo, $bar) = $data =~ $pattern) { die "horribly!"; } ...

        I usually just go

        my ($foo, $bar) = $data =~ /$pattern/ or die "horribly!";

        ihb

        See perltoc if you don't know which perldoc to read!