JoelFan
JoelFan

Reputation: 38714

modify $_ if no parameters specified

I have this trim routine:

sub trim {
    for (@_) {
        s|^\s+||;
        s|\s+$||;
    }
}

It trims whitespace "in-place", i.e.:

trim $x, $y;

will trim the whitespace of $x and $y (modifying them).

How can I improve trim so that it lets me call it like:

trim;

and that will be the same as:

trim $_;

?

Upvotes: 8

Views: 857

Answers (3)

ikegami
ikegami

Reputation: 386461

If your trim just took one arg, you could use the _ prototype.

sub trim(_) {
   for ($_[0]) {
      s|^\s+||;
      s|\s+$||;
   }
}

Then all of these would work:

{ local $_ = "  abc  "; trim;    say;    }
{ my    $_ = "  abc  "; trim;    say;    }
{ my    $x = "  abc  "; trim $x; say $x; }

But since your trim takes a list of args, you can't use the _ prototype. You have to use @_ ? @_ : $_.

sub trim {
   for (@_ ? @_ : $_) {
      s|^\s+||;
      s|\s+$||;
   }
}

But that means it won't work with lexical $_.

{ local $_ = "  abc  "; trim;    say;    }
{ my    $_ = "  abc  "; trim;    say;    }  # XXX $::_ changed instead of $_
{ my    $x = "  abc  "; trim $x; say $x; }

Upvotes: 4

Eric Strom
Eric Strom

Reputation: 40152

All you need to do is replace @_ with @_ ? @_ : $_ in the argument to the for loop.

sub trim {
    for (@_ ? @_ : $_) {
        s|^\s+||;
        s|\s+$||;
    }
}

Per ikegami's answer, there is the nagging issue of what to do about the blight that is lexical $_ (declared with my $_;) since that version of $_ is not global, but bound to a lexical pad.

The (_) prototype is one way to solve this problem, but it also means that the subroutine only takes one argument, and that the argument has scalar context, which makes it just as bad as the ($) prototype (due to unintended consequences of scalar context).

Whenever trying to do magic on lexicals, the module PadWalker comes in handy. So here is a version that works correctly on lists, works on both lexical and global $_, and it does not impose scalar context on the call site:

use PadWalker 'peek_my';

sub trim {
    my $it;
    unless (@_) {
        my $pad = peek_my 1;
        $it = $$pad{'$_'} || \$::_
    }
    for (@_ ? @_ : $$it) {
        s/^\s+//;
        s/\s+$//;
    }
}

{local $_ = " a "; trim;    say "[$_]"}  # prints "[a]"
{my $_    = " a "; trim;    say "[$_]"}  # prints "[a]"
{my $x    = " a "; trim $x; say "[$x]"}  # prints "[a]"

Personally, I just avoid lexical $_ and ugly problems like this just disappear. But if you need to support it, this is a way.

Upvotes: 11

user554546
user554546

Reputation:

Just check to see if there are no arguments provided:

use strict;
use warnings;

$_=" abc ";

sub trim
{
    if(@_)
    {
        foreach my $i(@_)
        {
            $i=~s/^\s+//;
            $i=~s/\s+$//;
        }
    }
    else
    {
        s/^\s+//;
        s/\s+$//;
    }
}

trim;

print "!$_!\n"; #Throwing in exclamation points just to make sure white space is gone.

This produces the output

!abc!

Upvotes: 11

Related Questions