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

I want to store the return values from a sub, and return them later, but I want to call the sub in the same context as the original caller. It works great doing it the obvious way:

my (@r, $r); if (wantarray) { @r = &$code } else { $r = &$code } # ... later return wantarray ? @r : $r;

I was wondering if there was perhaps a less obvious way. My main problems with this code are: (1) wantarray is checked twice, and (2) &$code is repeated. (Before anyone asks: yes, I am aware that @_ is being passed implicitly. This is the behavior I want.)

Problem (1) could be trivially "solved" by storing wantarray's return value, but then that variable would have to be checked twice. It's not really the call itself that bothers me, but the fact that the logic is duplicated. I'm not sure if it's possible or even desireable to avoid this, but maybe there's some idiom I am unaware of.

I thought I could solve problem (2) with the following:

wantarray ? @r : $r = &$code;

But that gives me an error, Assignment to both a list and a scalar. (Again, before anyone asks: no, it's not a precedence problem. I even tried adding appropriate parentheses just to be sure.)

Any ideas would be welcome. This isn't a showstopper, and I certainly have code that behaves as desired, but this seems like something that would have a nice little idiom or two to support it.

Replies are listed 'Best First'.
Re: Maintaining context of the caller
by ikegami (Patriarch) on Jul 04, 2005 at 19:02 UTC

    I'm guessing functions are called differently (internally) based on the expected context, which would explain why (wantarray ? @r : $r) = func() doesn't work. If so, that means the second call to the wrapped function cannot be eliminated.

    The following works if the call to the wrapped function is done at the end of the wrapping function:

    #!perl -l my $code = sub { wantarray ? 1 : 0 }; sub wrapper { # ... later return wantarray ? &$code : scalar &$code; } print wrapper; # 1 print scalar wrapper; # 0

    The following is a more general case:

    #!perl -l my $code = sub { wantarray ? 1 : 0 }; sub wrapper { my @r = wantarray ? &$code : scalar &$code; # ... later # Convert the array to a list to alter the # behaviour when assigning to a scalar. return @r[0..$#r]; } print wrapper; # 1 print scalar wrapper; # 0

    So what's that whole @r[0..$#r] junk? Let me explain by example:

    @r = &{ sub { return (4, 5, 6); } }; # 4, 5, 6 @r = &{ sub { my @r = (4, 5, 6); return @r; } }; # 4, 5, 6 @r = &{ sub { my @r = (4, 5, 6); return @r[0..$#r]; } }; # 4, 5, 6 $r = &{ sub { return (4, 5, 6); } }; # 6 $r = &{ sub { my @r = (4, 5, 6); return @r; } }; # 3 $r = &{ sub { my @r = (4, 5, 6); return @r[0..$#r]; } }; # 6

    Update: Non-code changes to make things more readable.

    Update: The following also works in the case where the call to the wrapped function is done at the end of the wrapping function. However, wrapper will not appear in the call stack (as seen by caller, cluck and confess, for example).

    #!perl -l my $code = sub { wantarray ? 1 : 0 }; sub wrapper { # ... later goto &$code; } print wrapper; # 1 print scalar wrapper; # 0
      The following works if the call to the wrapped function is done at the end of the wrapping function

      FYI, in the case of calling the wrapped sub at the end of the wrapper, one could simply do:

      sub foo { # ... &$code; }

      As for the goto version, that's a nice trick that I occasionally use, but isn't necessary in this case.

      The following is a more general case

      That's an excellent idea, always storing the results in an array and enforcing the context on the right hand side of the assignment. This is probably the approach I'll take. (Also, your explanation of the array slice wasn't necessary for me, but it's quite good, and others may benefit from it.)

      That's not so great. If scalar context is going to be used, then you're using unnecessarilly wasteful operations. Store the result in a scalar and return the scalar. Don't store into an array, construct a list of indexes, and return a list slice.

      Then again, I'm just against wasting resources on something so trivial. I write my code to call wantarray() multiple times because I know that's cheaper (it should be, anyway) than dealing with all that listy business.

        The aesthetical "correctness" of code will vary depending on one's point of view, but I think your concerns stem more from my question than ikegami's answer. I asked for a way to avoid duplicating the context-checking logic, and he provided it. I happen to think his is a very nice solution, but of course reasonable folks can disagree.

Re: Maintaining context of the caller
by brian_d_foy (Abbot) on Jul 04, 2005 at 19:28 UTC

    Check out the Hook::LexWrap implementation. I just wrote an article about this for The Perl Journal (although I'm not sure its published yet.

    Depending on what you're doing, Hook::LexWrap might actually do what you need. :)

    --
    brian d foy <brian@stonehenge.com>
      Depending on what you're doing, Hook::LexWrap might actually do what you need.

      Excellent suggestion, I'll take a look. And I will await that copy of TPJ with bated breath. :-)

Re: Maintaining context of the caller
by GrandFather (Saint) on Jul 04, 2005 at 20:21 UTC

    From the Camel:

    You can assign to the conditional operator if both the second and third arguments are legal lvalues (meaning that you can assign to them), and both are scalars or both are lists (otherwise, Perl won't know which context to supply to the right side of the assignment)


    Perl is Huffman encoded by design.
      Am I the only one confused by why this was quoted in reply to this node?

        From OP:

        I thought I could solve problem (2) with the following:

            wantarray ? @r : $r = &$code;
        But that gives me an error, Assignment to both a list and a scalar. (Again, before anyone asks: no, it's not a precedence problem. I even tried adding appropriate parentheses just to be sure.)

        The line of code is assignment to a conditional, but the values are not either both scalar or both list values. The Camel quote explaines that that is not an acceptable thing to do.


        Perl is Huffman encoded by design.

        Yep you probably are :-) That is the perldiag entry for the error message that the OP reported. It is not permitted to assign to a conditional operator where the the second and third arguments are of different type, as the OP has here.

        /J\

        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Maintaining context of the caller
by bsb (Priest) on Jul 05, 2005 at 02:56 UTC
    How's about: wantarray ? &$c : scalar(&$c)
    DB<2> $c = sub { my @a = (4..6) } DB<3> p scalar sub { @r = (wantarray ? &$c : scalar(&$c)); @r }->() 1 DB<4> p sub { @r = (wantarray ? &$c : scalar(&$c)); @r }->() 456 DB<5> x scalar sub { @r = (wantarray ? &$c : scalar(&$c)); \@r }->() 0 ARRAY(0x844e22c) 0 3 DB<6> x sub { @r = (wantarray ? &$c : scalar(&$c)); \@r }->() 0 ARRAY(0x844e22c) 0 4 1 5 2 6

      Perhaps I'm missing something subtle in your post, but how is this different from ikegami's reply?

Re: Maintaining context of the caller
by Codon (Friar) on Jul 05, 2005 at 19:24 UTC

    I don't see a way around having duplicated calls to &$code. You can change the way you call &$code based on what your calling context wants. You can even make the destination dependant on what the calling context wants, but this will require you to test wantarray twice; once to determine the lvalue and once to make the correct rvalue call.

    $code = sub { wantarray ? ('foot','ball') : 'soccer' }; sub foo { my ($r, @r); # two possible returns (wantarray ? (@r) : ($r)) = # slight of hand to get the +correct lvalue (wantarray ? (&$code) : &$code); # and make the correct rvalu +e call return $r || @r # return that which is defin +ed } $x = &foo(); @x = &foo(); print $x; print @x;

    This gives you the ability to do something after your call to &$code. If you did not need this, you could more susictly say:

    $code = sub { wantarray ? ("foot","ball") : "soccer" }; sub foo { wantarray ? (&$code) : &$code } $x = &foo(); @x = &foo(); print $x; print @x;

    Ivan Heffner
    Sr. Software Engineer, DAS Lead
    WhitePages.com, Inc.

      Thanks for the ideas. I'll chew on them a while and see how they digest. A quick note about your more succinct way. As I also noted in my reply to ikegami, an even more succinct way would be:

      sub foo { &$code }

      The context would propagate automatically.

      Update: to prove it to myself, I ran the following code:

      $ perl -le ' my $code = sub { print wantarray ? "list" : "scalar" }; sub foo { &$code } () = foo; foo'

      And it printed:

      list scalar

      Which verifies that the context propagates.