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

Contextual Monks,

If I want a subroutine that works with strings to have the ability be called in a scalar or void content, either:

v: do_something_to(\$this_var);
s: my $this_var = do_something_to($that_var);

Is this the best (only) approach?
sub name { my ($arg) = @_; if (ref $arg) { $$arg = 'x'; return; } else { return 'x'; } }

Replies are listed 'Best First'.
Re: Scalar and void subroutines
by philcrow (Priest) on Nov 10, 2005 at 21:13 UTC
    Use wantarray inside the sub to tell how it was called. That will be undef for void context, true for list context, and false (but defined) for scalar context:
    sub name { ... if (not defined wantarray) { # do your void context behavior } elsif (wantarray) { # do your scalar context behavior } else { # do your list context behavior } }
    Phil
        if (not defined wantarray) {
      I try to use and, or, and not only for flow control to minimize the potential for precedence mishaps (e.g. not defined foo later being changed to not defined foo && defined bar when ! defined foo && defined bar was meant). It may look a little funny to have a ! before defined at first, but you do get used to it.
Re: Scalar and void subroutines
by merlyn (Sage) on Nov 10, 2005 at 21:40 UTC
    Knowing how much trouble this sort of interface was for HTML::Entities::encode_entities, I'd suggest you rethink your choice.

    Keep in mind that there are a few odd "context at a distance" issues with normal Perl coding. It's hard to prove that something is in "void" context at first glance.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Is Contextual::Return a solution to this merlyn, or are subroutines slippery no matter the case?
Re: Scalar and void subroutines
by ikegami (Patriarch) on Nov 10, 2005 at 21:14 UTC

    You could avoid some duplication by using the following syntax:

    sub name { my $arg = ref $_[0] ? $_[0] : \do{my $anon = $_[0]}; # Do stuff. $$arg =~ s/^un//; return $$arg; }

    or using an alias instead of a reference:

    sub name { our $arg; local *arg = ref($_[0]) ? $_[0] : \do{my $anon = $_[0]}; # Do stuff. $arg =~ s/^un//; return $arg; }

    Test code:

    You could also check the calling context and avoid reference altogether:

    sub name { our $arg; local *arg = defined wantarray ? \do{my $anon = $_[0]} : \$_[0]; # Do stuff. $arg =~ s/^un//; return $arg; }

    but I'd avoid that since the it's not obvious what's going to happen when reading the call:

    name($i); # In place $j = name($i); # Not in place sub test { name($_[0]) } # unknown!!! sub test { name($_[0]) } test($i); # In place sub test { name($_[0]) } $j = test($i); # Not in place

    Update: Showed why wantarray is a bad idea instead of just saying it is.

    Update: Fixed bug found by eff_i_g. That'll teach me to test without warnings.

    Update: Oops, I forgot I should be able to read the argument, not just write to it. Fixed by changing \do{my ...} to \do{my ... = $_[0]}.

      ike:

      The return here errors if called as: name(\$var)
      sub name { my ($arg) = @_; if (not ref $arg) { $arg = \$arg; } # Do stuff. $$arg = 'changed'; return $$arg; }
      If I remove the return $$arg; it still works in either context because of that sneaky little 'return the last value' trick :D

      Thanks!
      I had a problem with the previous sub (when calling it in scalar context and not passing a reference). I was able to get the following to work:
      sub name { #%% reference argument if it is not a reference my $string = ref $_[0] ? $_[0] : \$_[0]; # processing #%% use return caveat $$string; }

        That's no good either. It clobbers $i in $j = name($i);.

        The fix is to change \do{my ...} to \do{my ... = $_[0]} in my subs. (Just fixed in grandparent.)

Re: Scalar and void subroutines
by GrandFather (Saint) on Nov 10, 2005 at 21:21 UTC

    There must be something more here eff_i_g for it to be important to distinguish between void and scalar context.

    Your sample code doesn't distinguish between void and scalar context of course. It simply provides a (somewhat hacky) way of signaling to the sub that you don't want a value returned. You could do something similar in a rather more up front way by:

    do_something_to(\$this_var, 'No return required'); sub do_something_to { if (! $#_) { $$_[0] = 'x'; return; } return 'x'; }

    but that's pretty ugle really :).


    Perl is Huffman encoded by design.
      You're right; my description was iffy. What I meant was:

      If called in void context, I am expecting a reference to be passed.
      If called in scalar context, I am expecting... either, actually, but there must be a reference in void.

      Sorry about that.

        So you could:

        sub name { die 'ref arg required in void context' if ! defined wantarray and ! +ref $_[0]; my ($arg) = @_; if (ref $arg) { $$arg = 'x'; return; } return 'x'; }

        as a check that you've got a ref param in void context.


        Perl is Huffman encoded by design.