in reply to Re: Calling subroutine in a package from other perl sub.
in thread Calling subroutine in a package from other perl sub.

It is a dangerous style to call functions with the
&hello_world;
notation because (as perlsub mentions) you get an implicit argument list passed that you probably did not intend.

Also note that just calling something a module doesn't give you any protection if it doesn't use a package statement internally. When loading stuff from a file that doesn't use package you can always do it yourself by declaring a package and then using do to load it.

# Loads a file into a package. sub load_lib { my $file = shift; my $pkg = shift; my $ret = eval "package $pkg; do '$file';"; if (not defined($ret)) { $@ and confess("Cannot parse '$file': $@"); $! and confess("Cannot load '$file': $!"); warn("Loading '$file' did not return a defined value"); } $ret; }
Note that the semantics here are slightly more generous than Perl's requirements of a module. I only warn if you don't get a defined value back. But the reason that I do this is that if it doesn't return something defined I cannot tell the difference between a system error caused executing the file and a system error caused by trying to read the file.

If that restriction bothers you, you can explicitly read the file's contents into a string with the package prepended and do your own eval of that. You may wish to look at the advice at the end of perlsyn on how to control the output of the error message though. (Put the package before the preprocessor message. There is a bug in 5.005 that causes a preprocessor message on the first line of an eval to be ignored. Besides which you need to do this to make the line-number you get be reasonable. :-)

Note that none of these solutions will help much if you have a script that does an exit somewhere. There are ways to override that, but if you are getting to this kind of detail, you probably wanted to make stuff into a module. :-)

Replies are listed 'Best First'.
Re: Re (tilly) 2: Calling subroutine in a package from other perl sub.
by zzspectrez (Hermit) on Jan 17, 2001 at 06:13 UTC

    I never quite understood why this is considered dangerous style. This is the old style of calling subroutines. If you do not predeclare you subs then you either have to &sub or sub() to not get a bareword error with use strict

    When you call a subroutine like &sub I understand that you are passing the value of @_ to the sub or the equivalent of sub(@_). If you call sub(), you instead are passing a null list. But exactly how is passing @_ dangerous if the sub is not using any passed variables?

    I think not knowing the following is more dangerous.

    #!/usr/bin/perl -w use strict; my @test = ("one","two","three"); my $x = "one"; my $y = "two"; my $z = "three"; test1($x,$y,$z); print "$x\n$y\n$z\n\n"; test2($x,$y,$z); print "$x\n$y\n$z\n"; test1(@test); print join "\n", @test, "\n"; test2(@test); print join "\n", @test, "\n"; sub test1 { my $x1 = shift; my $y1 = shift; my $z1 = shift; $_[0] = "test1: 1"; $_[1] = "test1: 2"; $_[2] = "test1: 3"; } sub test2 { my ($x2,$y2,$z2) = @_; $_[0] = "test2: 1"; $_[1] = "test2: 2"; $_[2] = "test2: 3"; }
    Output:
    one two three test2: 1 test2: 2 test2: 3 one two three test2: 1 test2: 2 test2: 3

    Someone mind explaining this? In more detail than " '@_' is a local array, but its elements are aliases for the actual scalar paramters." What really gets me is how useing shift changes the behavior! Why?

    zzSPECTREz

      First of all it is dangerous because the possibility for confusion is high. Sure, you may know to do it only when the called subroutine is unwilling to take arguments, but will the person who learns from your code know that?

      As for your cute test function, it is really simple. Perl scalars are always references to data, not the actual data. So if you want to convert a list of scalars into a list, it is substantially more efficient to just make a list of pointers to the data than really copy. And that is what Perl does when passing data into a function. However when you assign in Perl you assign by value, not reference.

      So your 3 variables coming in are really passed in by reference, so in test2 you are able to change the original variables (whether the originals were from a scalar or an array) through the references. (ie @_ is just a list of pointers back to the original variables.) In test1 you threw away the three references and then assigned to new variables. Well if you no longer have the three variables, then assigning to new ones won't change a thing.

      And as you note this works just as well if you have the original argument list being 3 variables or an array with 3 things.

      So the rule is that assignment is by value, and arguments are passed into functions by reference. You probably understand this correctly if you can puzzle out the following example:

      ($x, $y, $z) = 'x'..'z'; print "Original values.\n"; show_vals(); print "This rotates them.\n"; rotate($x, $y, $z); show_vals(); print "Why doesn't this?\n"; rotate_not($x, $y, $z); show_vals(); sub rotate { @_[-1..($#_-1)] = @_; } sub rotate_not { @_ = @_[-1..($#_-1)]; } sub show_vals { print "\$x: $x\n\$y: $y\n\$z: $z\n\n"; }

        Well, I would think it is because in sub rotate, you are assingning the data in @_ (x,y,z) to the aliases (references $x,$y,$z) indexed @_[-1..($#_-1]. However, in rotate_not, you are assinging the data (array slice) from @_[-1..($#_-1] to the array @_ and not the references. So you are overwriting the references to $x,$y,$z and not updating them.

        zzSPECTREz

      A bit of a different explanation...

      Let's pretend we are in Perl4 so there are no \ and -> operators to deal with references so we can use them to talk about how Perl itself deals with aliases.

      Then calling test($x,$y,$z) sets @_ to (\$x,\$y,\$z). So if you do $_[0]= "new val" without having modified @_ then you end up doing ${\$x}= "new val" because Perl will see that $_[0] currently contains a reference to (alias of) $x and so modifying $_[0] also modifies $x.

      Well if you do my $x1= shift then you are left with @_ containing (\$y,\$z). Do that two more times and @_ is empty. So $_[0]= "new val" ends up putting a value into @_ where no value was before. So there is no reference (alias) for Perl to implicitly dereference so, of course, $x is not changed in that case. So you end up with @_ having ("new val") or ("x2","y2","z2") and @_ just gets thrown away in the end.

      Also, when @_ is empty, doing $_[0]= "x" finds $_[0] not being an alias to anything and so creates a new scalar and sets $_[0] to be an alias to it (the only alias at that point).

      I hope that helps some.

      Update: And calling test(@v) sets @_ to (\$v[0],\$v[1],\$v[2]) (if @v has three elements) (which could be written as \(@v) in Perl5). But doing @_= @v copies the values rather than making aliases.

              - tye (but my friends call me "Tye")

        Yes. This is a good explanation of the diferences between shift and accessing @_ directly. After I shift, the alias is gone from @_. So its obvious I cant modify the original value.

        However, things dont exactly work like this though. And an alias is not the same as a reference. Correct? Because if test($x,$y,$z) does @_ = (\$x, \$y, \$z) then in then in the code you would have to dereference the variable after you shift them to access them. They would be references not scalar data. my $x = shift @_; print $$x;

        zzSPECTREz

        NOTE: I appreciate your responses. You allways get me thinking.