[comp.lang.perl] call-by-reference, $#_, and local

tell@currituck.cs.unc.edu (07/11/90)

I think I may have found two (possibly related) bugs in Perl 3.0 pl 18, or at
least some really subtle points.  I've been able to reproduce them with the
short programs below, extracted from a much larger program.
I'm trying to pass several arrays to a subroutine by reference so the
subroutine can fill them in.
-----------------------------------------------------------------------------
Problem 1.
Subroutine next_wire_al reads a record from a file and 
attempts to use $#_ (the number of arguments)
to behave somewhat differently in the presence of various arguments.

Argument 0 is a string that is set by the routine.
Arguments 1, 2, and optionaly 3 are arrays passed by reference with with "*."
Argument 3 can be omitted.

When the test "if($#_ == 3)" is present, the third argument gets munged as the
routine returns; the @attribs array prints out as empty in the calling
routine.  The value of $#_ printed is always correct, and the if() always does
the right thing as evidenced by the print within it.  Stepping through with
the debugger suggests that the call-by-reference is not working with the
"if($#_ == 3)" there (@wattribs is not identical to @attribs in this case).

So, what's going on?   I've read the manual several times and don't see
anything special about $#_.

#!/usr/local/contrib/bin/perl

&read_wirelist;

sub read_wirelist
{
	local($wire, @ports, @power, @attribs);

	&next_wire_al($wire, *ports, *power, *attribs);
#	&next_wire_al($wire, *ports, *power);

	print "wire=$wire\n";
	print "ports = @ports\n";
	print "power = @power\n";
	print "attribs = @attribs\n";
}

sub next_wire_al
{
	local(*mports) = $_[1];
	local(*pnames) = $_[2];
	local(@allattribs);
	local($do_wattribs);

	print "$#_ args\n";

## Comment out the next line and the closing brace and the program works OK.
	if($#_ == 3) {
		local(*wattribs) = $_[3];
		print "getting attributes\n";
		$do_wattribs = 1;
	}
		# a dummy line - we really read a file
	$line = "D0 MP=D0 type=bidirectional PWR=VGG";

	($_[0], @allattribs) = split(' ', $line);

	@mports = grep(s/^MP=//, @allattribs);   # module ports
	@pnames = grep(s/^PWR=//, @allattribs);  # power names
	if($do_wattribs) {
		@wattribs = grep(/\w*=\w*/, @allattribs); 
		if ($#wattribs>=0) {
			print "attribs = ",join(';', @wattribs), "\n";
		}
	}
	1;
}
			
-----------------------------------------------------------------------------
Problem 2:
In this shorter bit of code, the local(@at) call in get_at appears to
hide @at in routine one, even though we've passed a reference to it.
If this behavior is correct, it seems really counterintuitive.  I would
think that taking *at in the call to routine get_at would put the "pointer"
to the original array into $_[0], and that later calling local(@at) should
only hide that original array @at when it is accessed by name (creating a
new @at, but not changing where the "pointer" from the * operator points.)

sub one
{
	local(@attribs, @at);
	@attribs = ("foo");

	&get_at(*at);

	print "attribs = @attribs\n";
	print "at = @at\n";

}

sub get_at
{
	local(@at);	# change @at to @foo and program works OK.
	local(*at2) = $_[0];

	push(@at2, "got_at");
}

&one;


perl -v says:
>$Header: perly.c,v 3.0.1.5 90/03/27 16:20:57 lwall Locked $
>Patch level: 18
>
>Copyright (c) 1989, 1990, Larry Wall
>
>Perl may be copied only under the terms of the GNU General Public License,
>a copy of which can be found with the Perl 3.0 distribution kit.


While I've got everybody's attention (especially Larry's) is there any chance
that perl -d will ever be usable on 'do' files (library routines, for
example)?  How about hooks so one can look at the arguments to a subroutine-
glancing through perldb, I understand why with the current debugger
implentation $_[0] gives me the line number and not what I expect at the point
where the program is stopped, but it is really confusing at times.

Thanks for any help & advice,
Steve
--------------------------------------------------------------------
Steve Tell					tell@wsmail.cs.unc.edu
CS Grad Student, UNC Chapel Hill.
"If you insist on spending $10000 on 68030 technology, may we humbly
suggest you buy three Amiga 3000's."

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (07/17/90)

In article <15096@thorin.cs.unc.edu> tell@currituck.cs.unc.edu () writes:
: I think I may have found two (possibly related) bugs in Perl 3.0 pl 18, or at
: least some really subtle points.  I've been able to reproduce them with the
: short programs below, extracted from a much larger program.
: I'm trying to pass several arrays to a subroutine by reference so the
: subroutine can fill them in.
: -----------------------------------------------------------------------------
: Problem 1.
: Subroutine next_wire_al reads a record from a file and 
: attempts to use $#_ (the number of arguments)
: to behave somewhat differently in the presence of various arguments.
: 
: Argument 0 is a string that is set by the routine.
: Arguments 1, 2, and optionaly 3 are arrays passed by reference with with "*."
: Argument 3 can be omitted.
: 
: When the test "if($#_ == 3)" is present, the third argument gets munged as the
: routine returns; the @attribs array prints out as empty in the calling
: routine.  The value of $#_ printed is always correct, and the if() always does
: the right thing as evidenced by the print within it.  Stepping through with
: the debugger suggests that the call-by-reference is not working with the
: "if($#_ == 3)" there (@wattribs is not identical to @attribs in this case).
: 
: So, what's going on?   I've read the manual several times and don't see
: anything special about $#_.

There's nothing special about $#_.  There is something special about using
local() inside a BLOCK.

: 	if($#_ == 3) {
: 		local(*wattribs) = $_[3];
: 		print "getting attributes\n";
: 		$do_wattribs = 1;
: 	}
:	# [intermediate code omitted]
: 	if($do_wattribs) {
: 		@wattribs = grep(/\w*=\w*/, @allattribs); 
: 		if ($#wattribs>=0) {
: 			print "attribs = ",join(';', @wattribs), "\n";
: 		}
: 	}

That local there means that *wattribs is local to your conditional.  It's
out of scope by the time you try to write to it.

: Problem 2:
: In this shorter bit of code, the local(@at) call in get_at appears to
: hide @at in routine one, even though we've passed a reference to it.
: If this behavior is correct, it seems really counterintuitive.  I would
: think that taking *at in the call to routine get_at would put the "pointer"
: to the original array into $_[0], and that later calling local(@at) should
: only hide that original array @at when it is accessed by name (creating a
: new @at, but not changing where the "pointer" from the * operator points.)

When you pass in *at to your subroutine, it's passing a reference to the
symbol table entry for everything named "at".  You then say local(@at),
which replaces a specific pointer within that symbol table entry.  Then
you use $_[0] to make a local symbol table entry, but the entry being
referenced has already been modified by the previous local.

The basic problem is the semantics of local, which currently just replaces
the global pointer temporarily, saving the real global pointer in the meantime.
The interaction of dynamic scoping and call-by-reference sometimes is
counter-intuitive, as you say.

To fix this would require making a local symbol table entry for every
localized variable, not just for *variables.  I'm looking at ways to
do that logically without imposing too much of a time penalty, but it's
not a trivial problem.

One workaround is to use packages where appropriate to put service routines
into a different namespace than the client routines.  This doesn't help
if you call yourself recursively, but in those cases you can just pick your
variable names carefully.

: While I've got everybody's attention (especially Larry's) is there any chance
: that perl -d will ever be usable on 'do' files (library routines, for
: example)?  How about hooks so one can look at the arguments to a subroutine-
: glancing through perldb, I understand why with the current debugger
: implentation $_[0] gives me the line number and not what I expect at the point
: where the program is stopped, but it is really confusing at times.

Someday the debugger will be useful in "eval" contexts, but it's not on the
top of my list.  That'd be a good project for someone masochistic enough
to enjoy looking at the innards of Perl.  Please talk to me before you try
it, however.

A mod to allow looking at a local copy of @_ directly wouldn't be difficult--
just copy @args back into @_ at the appropriate point in sub DB--perhaps
only if the current command contains an underline.  Hmm, you might have
to do it all the time if a break or trace command referred to @_.

Rather more work would be to allow mods to @_ to be copied back into the
real @_.  This is close to impossible without mods to C code, I think.

Larry