Wyrdweaver has asked for the wisdom of the Perl Monks concerning the following question:

I'm in the process of writing a couple of "shortcut" functions, but I'm unsure about implementing the best semantics for the functions. I'm not sure whether the functions should silently take '$_' as an argument when the function is called with zero arguments as seems to be the case with the standard "shortcut" idiom. Using trim from Trim::Text as an example:
sub trim { @_ = @_ ? @_ : $_ if defined wantarray; # disconnect aliases in no +n-void context for (@_ ? @_ : $_) { s/\A\s+//; s/\s+\z// } return wantarray ? @_ : "@_"; }
On the one hand, it allows perlish natural use of the function, such as:
foreach (@args) { <...>; trim; <...>; }
But, on the other hand, empty argument lists may bite you by operating unexpectedly on '$_':
foreach (@args) { my @b; <...> @b = (); trim(@b); # silently the same as trim($_) since @b has no elements <...>; }
I'm honestly tempted to rewrite the "shortcut" function as:
sub trim { if ( !@_ && !defined(wantarray) ) { carp 'Useless use of trim with + no arguments in void return context (did you mean "trim($_)"?)'; ret +urn; } if ( !@_ ) { carp "Useless use of trim with no arguments"; return; + } @_ = @_ if defined wantarray; for (@_) { s/\A\s+//; s/\s+\z// } return wantarray ? @_ : "@_"; }
In the best of all worlds, I'd rewrite the function to differentiate between "trim()" and "trim( () )". But, given the implementation of subroutine argument passing, I haven't come across or originated any way of doing that. (Let me know if you have...)

So, what's the prevailing wisdom on this?

Thanks.

- Wyrd

Replies are listed 'Best First'.
Re: Is silent use of $_ for empty argument lists reasonable for "shortcut" functions? (E2CUTE)
by tye (Sage) on Aug 26, 2007 at 00:49 UTC

    Most of the time I wouldn't be tempted to write functions that operate on $_ if given no arguments. trim($_) is just not a big hardship compared to trim() (although I do use implicit $_ in some cases, I usually don't with chomp(), for example).

    If I had a function that used $_ when given no arguments, then I'd likely not support taking a list of arguments (because of the ambiguity you noticed). Though, if you aren't likely to run into a relatively old version of Perl, then there is a somewhat ugly way around the ambiguity problem that I'm reluctant to mention because somebody would probably use it:

    - tye        

      Interesting, I never thought of that method to figure out the arguments. But you're right, forcing the 1st argument into a reference breaks too many usual idioms (such as trim(<STDIN>) as you mentioned).

      I wish that "@_" was undef when a function is called with no arguments (as opposed to called with an empty array). Or, that there was some other way to detect no arguments without forcing the 1st argument into a reference.

      I appreciate the "spoiler". I'll add it to my perl KB (even if I never use it).

      - Wyrd

        I wish / ... / that there was some other way to detect no arguments without forcing the 1st argument into a reference.

        There is, kind of.

        sub foo { (caller(0))[4] or return foo($_); ...; }
        This will make $_[0] become aliased with $_ if foo was called with &foo and without the parenthesis. Personally I wouldn't use this. It will only lead to trouble.

        lodin

        I thought about the undef vs. empty thing myself. It'd be rare, but it might break things for some people.

        Then I thought about $#_, which turns out to be -1 for both cases. I imagine changing it to, say, -2 for the empty list wouldn't break much, but there's always that chance. It also seems a little too magical and would definitely be a FAQ if it was done that way.

        I had pretty much the same thoughts about $_[-1]. Making it special for this case could be handy, but it's probably a bad idea.

        It'd be really nice if there was a variable or a built-in one could check that says whether there were any arguments to a function or not without enumerating them. Of course, a new built-in could break programs that have name conflicts with the new built-in.

        sub foo { if ( has_args ) { foreach ( @_ ) { # ... } } else { # ..., but with $_ } }
        Even an additional field on the end of caller could work I suppose, but (caller(0))[4] seems unsuitable at present. Perhaps if it returned 0 for &foo, 1 for foo(...), and zero-but-true for foo() it would be handy here. The nice advantage to adding to the end of an array that already gets returned is that it only breaks where one is taking [-1] as an element from the array... which is the same issue I guess as above.

        I like the zero/one/zero-but-true option for the fact that currently people probably only test for truth and not for equality to one, so making a special case such as empty args with parentheses or an empty list (pretty much the same for this scenario) probably wouldn't break anything. Of course, someone somewhere would get paged into work for a broken production system if just to prove me wrong.

Re: Is silent use of $_ for empty argument lists reasonable for "shortcut" functions?
by graff (Chancellor) on Aug 26, 2007 at 01:03 UTC
    I may be missing some subtleties here, but if you want a no-arg call to operate on $_ (handy shortcut) and you don't want a call with an empty array arg to operation on $_ by mistake (nasty unexpected side-effect), the way to write the sub would (I think) be like this:
    sub trim { my @args = ( @_ ) ? @_ : $_; for ( @args ) { s/\A\s+//; s/\s+\z//; } return wantarray ? @args : $args[0]; }
    If you pass an empty array to that verion, it will not modify $_.

    Update -- test suite:

    #!/usr/bin/perl use strict; sub trim { my @args = ( @_ ) ? @_ : $_; for ( @args ) { s/\A\s+//; s/\s+\z//; } return wantarray ? @args : $args[0]; } $_ = " test "; print "test 1: ==" . trim . "==$_==\n"; $_ = " 1234 "; my @b = (); trim( @b ); print "test 2: ==". $_ . "==@b==\n"; my @c = ( " a ", " b ", " c " ); my $d = join "", trim( @c ); print "test 3: ==". $_ . "==" . $d . "==@c==\n"; __OUTPUT__ test 1: ==test== test == test 2: == 1234 ==== test 3: == 1234 ==abc== a b c ==

    Having updated "test 1" to print out both the return value of trim and $_, I see that $_ is never modified, which I gather is not what you want. Sorry -- I get your point now, and I don't have an answer.

      Test 1 is fine. $_ shouldn't be modified in that case. (Not void context.)

      Test 2 is the one that fails. It doesn't print == 1234  ==1234== as it should.

Re: Is silent use of $_ for empty argument lists reasonable for "shortcut" functions?
by enemyofthestate (Monk) on Aug 25, 2007 at 22:54 UTC
    Try something like:
    sub trim { my (@out) = @_; foreach (@out) { s/^\s+//; s/\s+$//; } return wantarray ? @out : $out[0]; }
    I think this avoids your problem.
      No, the "shortcut" idiom modifies its arguments in place if called in void context. I only want to disconnect aliasing if the function is called in a non-void context.

      That way the function will return value(s) when asked or operate on it's arguments in place when called appropriately. Eg:
      @strs = ( " a string ", " b string "); $s = " my string "; $a = trim($s); # $a gets the trimmed version of $s @b = trim(@strs); # @b gets trimmed versions of all strings in @strs trim($s); # trim $s in-place trim(@strs); # trim all @str in-place
      - Wyrd
      How does this code differentiate between being call with no args and being called with an empty list?
        It doesn't. I misunderstood your question.
Re: Is silent use of $_ for empty argument lists reasonable for "shortcut" functions?
by girarde (Hermit) on Aug 27, 2007 at 16:36 UTC
    Not knowing what the function is I can't form an opinion as to whether it should be allowed to scarf up $_ if it's passed nothing.

    I'd be inclined not to permit this if only to maintain the law of least surprise. If it needs to operate on $_ sometimes there are ways to test for that and make it happen.