?

Log in

No account? Create an account

$_

« previous entry | next entry »
Nov. 3rd, 2012 | 02:50 pm

Argh, Perl... I love you, but sometimes you drive me nuts. Take this program:

#!/usr/bin/perl

use v5.10.0;

my @dir = ("foo");

sub process_dir {
	my ($dir) = @_;
	1 while(glob("${dir}/*"));
}

say "dir = $dir[0]";
process_dir($_) foreach (@dir);
say "dir = $dir[0]";

which produces this output:

$ perl test.pl
dir = foo
dir =
$ 

This had me puzzled for a good 20 minutes. As far as I can tell, the reason why this is happening is that both the while loop in process_dir and the foreach loop in the main program implicitely assign to $_; in the latter case, though, it's not actually assignment as much as aliasing... and there is only one $_ that's globally shared among all code.

Who'da thunk that $_ is global that way, though? Checking in Programming Perl (the older 3rd edition), this is briefly mentioned on page 658, but I don't think I ever ran into it in practice, and I'm finding it counterintuitive, too. $_ is supposed to be the "topic" that is currently being talked about, and the loops are unrelated, in different scopes, separated by a function call. They're obviously having different conversations, as it were, talking about different things.

At least it means that the problem's easily fixed. But really, doesn't this mean that it's basically unsafe to use $_ anywhere in subroutines given that you can never be sure the caller might not be using it for something already? Maybe I should ask about that on perlmonks or so.

EDIT: Well, you can localize it to be on the safe side, of course, but that's clunky and inelegant. I expect Perl to be DWIMmier than that.

Link | Leave a comment | Share

Comments {9}

(no subject)

from: xviith_et_seq
date: Nov. 3rd, 2012 03:23 pm (UTC)
Link

Perhaps even more amusingly, a foreach with implicit $_ as the loop variable will (effectively) localize it dynamically via its mechanism of making it alias each of the elements in turn, but a while with an expression that implicitly stores into $_ will not. In the latter case, the expression that stores into $_ isn't really part of the loop construction, but more like a hidden normal assignment that's part of the condition, which is presumably the reason for the difference. The while case also applies to the more common while (<FH>), which is short for while(defined($_ = <FH>)), which more obviously does not localize $_, and that result is covered explicitly in that section of perlop. In fact the glob case is in the same section, because readline and glob are both spelled <> as they talk about them. This leads to more fun like that operator being interpreted as indirect-filehandle readline if there's a single scalar inside it, but as glob if there's a single scalar inside it that's followed by a space.

Perl can be like that.

Reply | Thread

Schneelocke

(no subject)

from: schnee
date: Nov. 3rd, 2012 05:51 pm (UTC)
Link

Perl can be like that.

Aye... although it usually isn't. :) The bit about <> being used as a short-hand for glob in addition to reading from a filehandle is more or less explicitely mentioned in Programming Perl as a design mistake, BTW:

These days, it's considered cleaner to call the internal function directly as glob($foo), which is probably the right way to have invented it in the first place. So instead you'd write

@files = glob("*.xml");

if you despise overloading the angle operator for this. Which you're allowed to do.

I think having an operator for globbing isn't a bad in principle, but having it be the same as the one for reading from a filename is asking for trouble.

Speaking of localizing $_, BTW, you can of course get around the above by means of explicit localization of $_ in the subroutine in question, to be able to use the topic while avoiding any potential clobbering further up the callstack. But that strikes me as less than elegant (although OTOH, it's one of the few instances I can think of where local is truely useful).



Edited at 2012-11-03 05:51 pm (UTC)

Reply | Parent | Thread

Jaffa

(no subject)

from: jaffa_tamarin
date: Nov. 3rd, 2012 05:07 pm (UTC)
Link

It's not just in Perl you get things like this. I have had problems porting shell scripts that use $* to access the argument list because on some O/S's (*cough*AIX*cough*) this is treated as a global and will be over-written if you call other scripts as subroutines.

Reply | Thread

Schneelocke

(no subject)

from: schnee
date: Nov. 3rd, 2012 05:40 pm (UTC)
Link

What, really? I'm glad I never had to do anything on AIX, then. That's pretty horrible.

Reply | Parent | Thread

(no subject)

from: xviith_et_seq
date: Nov. 4th, 2012 01:35 am (UTC)
Link

“as subroutines” meaning sourcing them with dot or source, or meaning executing them as though they were standalone programs?

Reply | Parent | Thread

(Deleted comment)

Schneelocke

(no subject)

from: schnee
date: Nov. 3rd, 2012 09:52 pm (UTC)
Link

Not a bad idea, but it sort of defeats the purpose of having $_ in the first place.

Reply | Parent | Thread

(Deleted comment)

Schneelocke

(no subject)

from: schnee
date: Nov. 3rd, 2012 10:45 pm (UTC)
Link

That's true. Nevertheless...

Reply | Parent | Thread