On the (much appreciated) perlmonks site, I found the following snippet that trims the spaces from both sides of a string:
sub trim {
@_ = $_ if not @_ and defined wantarray;
@_ = @_ if defined wantarray;
for (@_ ? @_ : $_) { s/^\s+//, s/\s+$// }
return wantarray ? @_ : $_[0] if defined wantarray;
}
I don't understand why the author goes to all the trouble of checking wantarray almost every line. Why not just trim the string, and have the programmer use map
when he is passing an array?
What is the difference between that trim, called like this:
my @test_array = ( 'string1', ' string2', 'string3 ', ' string4 ');
my @result = trim(@test_array);
Or a simple trim, called like this when one needs to trim an array:
my @test_array = ( 'string1', ' string2', 'string3 ', ' string4 ');
my @result = map { trim($_) } @test_array;
First of all it's better if you abstract that map away:
#e.1.
sub trim
{
my @ar = @_;
for (@ar) { s/^\s+//, s/\s+$// };
return wantarray ? @ar : $ar[0];
}
Second, consider the above example and compare it with:
#e.2.
sub trim
{
for (@_) { s/^\s+//, s/\s+$// };
}
what's the difference?
e.1. returns a new trimmed array, while e.2. modifies the original array.
Ok now, what does the original cryptic subroutine do?
It auto-magically (yeaah, it's Perl) modifies the original array if you are not assigning the return value to anything OR leaves the original array untouched and returns a new trimmed array if you are assigning the return value to another variable.
How?
By checking to see if wantarray is defined at all. as long as the function is on the right hand side and the return value is assigned to a variable "defined wantarray" is true (regardless of scalar/array context).
Breaking this down line by line since it hasn't been:
sub trim {
@_ = $_ if not @_ and defined wantarray;
# if there are no arguments, but a return value is requested
# then place a copy of $_ into @_ to work on
@_ = @_ if defined wantarray;
# if the caller expects a return value, copy the values in @_ into itself
# (this breaks the aliasing to the caller's variables)
for (@_ ? @_ : $_) { s/^\s+//, s/\s+$// }
# perform the substitution, in place, on either @_ or $_ depending on
# if arguments were passed to the function
return wantarray ? @_ : $_[0] if defined wantarray;
# if called in list context, return @_, otherwise $_[0]
}
I agree that the code gets a bit tedious with all of the wantarray
checks, but the result is a function that shares a level of flexibility with Perl's builtin functions. The net result of making the function "smart" is to clean up the call site (avoiding looping constructs, temp variables, repetition, ...) which depending on the frequency the function is used can meaningfully improve code readability.
The function could be simplified a little bit:
sub trim {
@_ = @_ ? @_ : $_ if defined wantarray;
s/^\s+//, s/\s+$// for @_ ? @_ : $_;
wantarray ? @_ : shift
}
The first two lines can be rolled into one, since they are doing the same thing (assigning to @_
) just with different source values. And there is no need for the outer return ... if defined wantarray
check at the end, since returning a value in void context doesn't do anything anyway.
But I would probably change the last line to wantarray ? @_ : pop
since that makes it behave like a list (last element in scalar context).
Once all is said and done, this allows the following calling styles to be used:
my @test_array = ( 'string1', ' string2', 'string3 ', ' string4 ');
my @result = trim @test_array;
my $result = trim $test_array[0];
trim @test_array; # in place trim
and even still supports the call site loop:
my @result = map trim, @test_array;
or more verbosely as:
my @result = map trim($_), @test_array;
and it can be used inside a while loop similar to chomp
while (<$file_handle>) {
trim;
# do something
}
Opinions about dwimmery in Perl are mixed. I personally like it when functions give me the flexibility to code the caller in a way that makes sense, rather than working around a function's rigid interface.
Probably the author wanted to mimic the behavior of the standard chomp
function. There is no need to do this in your own function.
man perlfunc chomp VARIABLE chomp( LIST ) chomp
[...] If you chomp a list, each element is chomped. [...]
Note that this is exactly how Text::Trim is implemented. See its description on various use-cases. Plays with wantarray
allows to distinguish various contexts and implement different semantics for each.
Personally I prefer just single semantics as it is easier to understand and use. I would avoid using $_
default variable or modification in place, in line with Nylon Smile's example 1.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With