How to code an implicit $_ argument?

T

Tim McDaniel

This is a function which trims leading and trailing whitespace from a
variable in-place, operating on $_ if there are no arguments provided.
(If the value is undef, it leaves it undef.) I feel a bit unclean
using "goto &sub" and hand-manipulation of @_. Opinions? Better ways
to do it?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#! /usr/bin/perl -w

use strict;

sub trim(;\$) {
if (@_ == 0) {
$_[0] = \$_;
goto &trim;
}
my $var = $_[0];
if (defined $$var) {
$$var =~ s/^\s+//;
$$var =~ s/\s+$//;
}
return $$var;
}

my $v = ' abc ';
trim $v;
if ($v ne 'abc') {
warn "\$v should be 'abc' but is '$v' instead.\n";
}

$_ = ' 123 ';
trim;
if ($_ ne '123') {
warn "\$_ should be '123' but is '$_' instead.\n";
}

exit 0;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The only clean way I see to do it is this, but I don't know whether
later Perl interpreters notice and optimize the tail recursion.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

....
sub trim(;\$);
sub trim(;\$) {
if (@_ == 0) {
return trim($_);
}
my $var = $_[0];
if (defined $$var) {
$$var =~ s/^\s+//;
$$var =~ s/\s+$//;
}
return $$var;
}
....

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In a recent message, someone mentioned that Perl 5.10 has the _
prototype character: if there is no matching argument, use $_.
But that doesn't work here, as the following code produces
Can't use string (" abc ") as a SCALAR ref while "strict refs"
in use at /home/tmcdaniel/local/test/084.pl line 7.
and "sub trim(\_)" causes
Malformed prototype for main::trim: \_ at
/home/tmcdaniel/local/test/084.pl line 15.

Also, where is _ documented? I don't see it in "man perlsub" for Perl
5.10 in Cygwin.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

....
sub trim(_) {
my $var = $_[0];
if (defined $$var) {
$$var =~ s/^\s+//;
$$var =~ s/\s+$//;
}
return $$var;
}
....

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
J

John W. Krahn

Tim said:
This is a function which trims leading and trailing whitespace from a
variable in-place, operating on $_ if there are no arguments provided.
(If the value is undef, it leaves it undef.) I feel a bit unclean
using "goto &sub" and hand-manipulation of @_. Opinions? Better ways
to do it?

sub trim {
if ( @_ ) {
s/^\s+//, s/\s+$// for @_;
}
else {
s/^\s+//, s/\s+$// for $_;
}
}



John
 
T

Tim McDaniel

sub trim {
if ( @_ ) {
s/^\s+//, s/\s+$// for @_;
}
else {
s/^\s+//, s/\s+$// for $_;
}
}

Oo-er. So I should depend on

The array @_ is a local array, but its elements are aliases for
the actual scalar parameters. In particular, if an element $_[0]
is updated, the corresponding argument is updated (or an error
occurs if it is not updatable).

instead of taking references explicitly or implicitly. Hrm. It's a
run-time error instead of compile-time, but it's much more elegant and
Perlish.

Two improvements occur to me.
s/^\s+//, s/\s+$// for $_;

has the effect of creating a new $_ and aliasing $_ to, um, old $_,
which is not needed. It would be easier to just do

s/^\s+//, s/\s+$//;
if ( @_ ) {
s/^\s+//, s/\s+$// for @_;
}
else {
s/^\s+//, s/\s+$// for $_;
}

looks very symmetric and therefore elegant. But the individual
actions have to fit in a comma expression. Also, the set of
statements are repeated, so any change has to be done in two places --
in particular, one part of the spec is that undef should be silently
ignored, but the above code causes two "Use of uninitialized value $_
in substitution (s///)" warnings.

My first version defined a return value, but I don't really need it,
since the function changes variables. I prefer to always return an
explicit value, if nothing else to prevent accidental communication of
the function's last expression, causing the caller to accidentally
depend on the implementation.

I think I'll do something along these lines:

sub trim(@) {
foreach (@_ ? @_ : $_) {
if (defined $_) {
s/^\s+//;
s/\s+$//;
}
}
return undef;
}

or something equivalent. Maybe I will go for

defined($_) && (s/^\s+//, s/\s+$//) for @_ ? @_ : $_;

Many thanks for the insight.
 
M

Martijn Lievaart

or something equivalent. Maybe I will go for

defined($_) && (s/^\s+//, s/\s+$//) for @_ ? @_ : $_;

This will have unexpected results when $_ is defined, but you do give the
function arguments. Suddenly it works on $_ while you expected it to work
on @_.

Just drop the first test and it looks fine.

M4
 
W

Willem

Tim McDaniel wrote:
) The only clean way I see to do it is this, but I don't know whether
) later Perl interpreters notice and optimize the tail recursion.
)
) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)
) ...
) sub trim(;\$);
) sub trim(;\$) {
) if (@_ == 0) {
) return trim($_);
) }
) my $var = $_[0];
) if (defined $$var) {
) $$var =~ s/^\s+//;
) $$var =~ s/\s+$//;
) }
) return $$var;
) }

I don't see why you would need prototyping for this:

sub trim
{
return trim($_) unless @_;
for (@_) {
s/^\s+//;
s/\s+$//;
}
return @_;
}

As far as I know, members of @_ are aliases for the function parameters, so
this should work directly on the given parameters.
And, of course, the for (@_) loop makes $_ aliases of the @_ members.

I wouldn't worry about the single tail-recursive call. If it turns out you
need the extra speed, just hand-inline the code.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
T

Tim McDaniel

This will have unexpected results when $_ is defined, but you do give
the function arguments. Suddenly it works on $_ while you expected it
to work on @_.

Why do you say that? "man perlsyn" says that a

Any simple statement may optionally be followed by a SINGLE
modifier, just before the terminating semicolon (or block ending).
The possible modifiers are:

if EXPR
unless EXPR
while EXPR
until EXPR
foreach LIST

(Pity about that "SINGLE":
s/^\s+//, s/\s+$// if defined($_) for @_ ? @_ : $_;
would look cleaner.)

So "&&" doesn't bind more loosely than "for". The "defined($_)"
refers to the new $_ defined by "for", and s/// works on that $_.

I've tested the statement with
$_ = ' narf ';
my $v = ' abc ';
my $w = "\tzyx \r ";
trim($v, $w);
$v and $w were trimmed and $_ was untouched, as specified.
Just drop the first test and it looks fine.

You didn't notice what I wrote in a previous article, that I want it
to silently ignore arguments that are undef, and not emit
"Use of uninitialized value $_ in substitution (s///)" messages.

It occured to me later that I could do

$_ and s/^\s+//, s/\s+$// for @_ ? @_ : $_;

But that's getting too much like line-noise for my taste.
 
T

Tim McDaniel

Quoth (e-mail address removed):
sub trim(_) {
my $var = $_[0];

my $var = \$_[0];

Wait, you can *do* that?! I knew you could assign to $_[expr] itself
to change the caller's argument, but you can take a reference to it
and use that to change it too?

.... clickity click ...

Sumbitch, you can! And it works for the aliasing in foreach too
(presumably because it's the exact same concept).

-- Tim McDaniel, (e-mail address removed)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#! /usr/bin/perl -w

use strict;

sub foo(_) {
my $var = \$_[0];
++$$var;
return undef;
}

$_ = 19;
my $v = 23;
foo($v);
print "v: $v\n";
($v == 24) || die;

foo();
print "_: $_\n";
($_ == 20) || die;

my @a = (123, 456, 789);
foreach (@a) {
my $var = \$_;
$$var *= 10;
}
foreach (@a) {
print "a value is $_\n";
die unless $_ % 10 == 0;
}

exit 0;
 
M

Martijn Lievaart

You didn't notice what I wrote in a previous article, that I want it to
silently ignore arguments that are undef, and not emit "Use of
uninitialized value $_ in substitution (s///)" messages.

Oops, sorry. However, you may wonder if this terseness is worth this
potential confusion.

M4
 
I

Ilya Zakharevich

IMHO that shouldn't be too difficult in xs

Of course. The semantic of aliasing is much simpler to implement than
that of copying the value.

The situation is the same as with lvalue-subroutines: it is not that
one needs tricks to make the return value writable - it is the
opposite: Perl core makes extra steps to make the return value of
"usual subroutines" non-modifiable.

The harder part is to find IN WHICH MODULE one may put this
functionality.

Ilya

P.S. And of course, passing the patch through (the mess of?) p5p
might be still tricky. IIRC, I may sit on a hundred of patches
to Perl implementing IMO very important functionality which
were ignored by p5p...
 
D

Dr.Ruud

Ilya said:
Dr.Ruud:

Can't locate Data/Alias.pm in @INC ...

It should be in the core.

Chip is implementing ":=", see p5p.

I am not sure that slot aliasing is included, but if so then it would
simply look like:

my @array = qw/ q w e r t y /;
my $key := $array[ 2 ];

or

my $key = "e";
my @array = qw/ q w # r t y /;
$array[ 2 ] := $key;
 
S

sln

Ilya said:
Dr.Ruud:

Can't locate Data/Alias.pm in @INC ...

It should be in the core.

Chip is implementing ":=", see p5p.

I am not sure that slot aliasing is included, but if so then it would
simply look like:

my @array = qw/ q w e r t y /;
my $key := $array[ 2 ];
^^
Suspiciously looks like a Pascal assignment operator.
Lets get even more confusing.
-sln
 

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
473,961
Messages
2,570,131
Members
46,689
Latest member
liammiller

Latest Threads

Top