With this example, I would like to learn the best method to code an adaptative subroutine.
I need a subroutine that trim text (actually, it is a pretext, my subroutine could have done anything else).
In order to be more versatile, I would like my subroutine to accept different types of arguments:
my @input = (' A ', ' B', 'C ');
my @trimmed = trim(@input);
trim(\@input);
my $out = trim($input[0]);
which returns 'A'
trim(\$input[0]);
which trim the first element of @input;
trim(\@a, \@b, \@c);
Each array of strings is trimmed
my $out = trim(\@a, ' A ', ' B');
$out = (qw/A B/); (behavior to be discussed)
Here's my current ugly solution:
sub trim {
state $re = qr/^\s+|\s+$/m;
my @a;
for(@_) {
if(ref eq 'SCALAR') { $$_ =~ s/$re//g; }
elsif(ref) { trim(\$_) for(@$_); }
else { push @a, s/$re//gr; }
}
return \@a if @a > 1;
return $a[0] if $a[0];
}
Is there any better solution of this implementation that support different kind of input as I suggested above ?
The main reason of this question concerns my final application in which I am carrying text that can be stored in a string, an array of strings or even a hash.
I suppose it is better to write:
trim(\@allmytexts);
align(\@allmytexts, align=>'right');
Than:
for(@allmytexts) {
trim($_);
align($_, align=>'right');
}
This question is off topic, and will likely be closed soon.
However, "Is it correct to ask for this type of behavior in such subroutines ?"
I would say no. I am far from clear about what it is you want this subroutine to do with the information in front of me, and, without referring to your documentation, I would certainly be struggling to remember how to call it if I hadn't used it for a day or two. You will find that everybody uses it by writing
$string = trim($string)
because that is what they remember.
It also looks like you're not clear yourself what some calls do, hence
my $out = trim(\@a, ' A ', ' B'); $out = (qw/A B/); (behavior to be discussed)
I suspect that you are used to a language with a different passing mechanism, and something like this would be useful. In Perl, everything is passed by alias; so an operation on an element of the @_
array is equivalent to the same operation on the actual parameter. That means that a subroutine could modify any writeable value if you pass that value as a parameter.
With that in mind, passing a reference to indicate that it is to be modified in-place is nothing more than a flag that dictates how the subroutine is to behave, together with the encumbrance that the reference has to be dereferenced before it can be used.
Ask yourself how often you have seen a commonly-used library function behave like this. For instance, there are separate calls to index
and rindex
to do very similar things, and the existing core operator lc
is reasonably similar to what trim
is trying to do but it only ever takes one parameter and returns the transformed result. sprintf
could be seen as a variant of printf
, if only it knew that, if the file handle parameter was a reference to a scalar, it should put the string into the variable instead of writing it to a file handle. But it wasn't designed like that.
I can only offer an alternative solution to a defined problem. You don't explain the real-life difficulty, and since you're not sure yourself what your trim
should do I doubt if anyone can help you to improve it.
Something else to ask yourself is, what should the return value be when a reference is passed and the value is modified in place? It may be obvious that it should return the value after it has been changed, but, since you're writing adaptive code, how about changing the return value depending on whether the call was in scalar, list, or void context? Or, even better, make trim
an lvalue subroutine so that you can write something like
my $n = q{ 999 };
++trim($n);