Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Determining subroutine return type

by satchm0h (Beadle)
on Feb 24, 2005 at 21:28 UTC ( [id://434264]=perlquestion: print w/replies, xml ) Need Help??

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

Imagine you have a reference to a subroutine that may return a reference to a list, or a reference to a hash, or a list, or a hash.

If we were only getting references back, life would be good :
sub determineType { $sub_ref = shift; $rval = $sub_ref(@_); return @$rval if( ref($rval) eq 'ARRAY' ); return %$rval if( ref($rval) eq 'HASH' ); }
What I need is some analog for non-reference return values. For instace, if the subroutine returns a list I don't want to evaluate it in scalar context and end up with the length.

Can I only reflect on references and not on base types?

Is there some way I can cast the return value of the subroutine to a reference on the fly; so that I may inspect it?

UPDATE

Thanks to all those who responded. Particularly phaylon, friedo, and ikegami. As I stated below, I'm basiclly trying to pass through the return value of the subroutine reference. Here is the "final" proof of concept.
sub execSubroutine { my $sub = shift; my @sub_ret = &{$sub}(@_); # Return an list if we get a list with more than one value # this will satisfy callers expecting lists and/or hashes return @sub_ret if (@sub_ret > 1); my $rval = $sub_ret[0]; # If we have a ref de-reference and return. if (my $type = ref($rval)) { return @$rval if ($type eq 'ARRAY'); return %$rval if ($type eq 'HASH'); } # If not a ref and the caller wants a list, give it to them if (wantarray) { return @sub_ret; } # Otherwise return a scalar return $rval; }
If you are interested in seeing the code I used to test this routine, drop me a line and I'll add it.

Replies are listed 'Best First'.
Re: Determining subroutine return type
by friedo (Prior) on Feb 24, 2005 at 21:38 UTC
    You can't really coerce the return value listwise, because the value gets put in the context of the function call. Rather, it's much easier to simply have your function return the right thing depending on its calling context. You can tell what kind of thing you need to return with the unfortunately named wantarray function. (It should really be called wantlist.)

    sub example { if(wantarray) { return (1,2,3); } return [1,2,3]; } my @array = example(); my $arrayref = example();

    All references are scalar, so you will still need to check them with ref. (Although perhaps it would be easier to redefine your subs so you always know what kind of reference they return.)

      Agreed. It would certainly be much simpler to have your function "return the right thing". But in the case where you are wrapping/extending a legacy code base, that may not be an option.
Re: Determining subroutine return type
by ikegami (Patriarch) on Feb 24, 2005 at 21:41 UTC

    The caller can't tell the difference between a list of one item and a scalar, so you won't get perfect results. Maybe the following will do what you want, though:

    sub determineType { my $sub_ref = shift; my @rval = $sub_ref(@_); return $rval[0] if @rval <= 1; return @rval if @rval >= 2; my $rval = $rval[0]; return @$rval if (ref($rval) eq 'ARRAY'); return %$rval if (ref($rval) eq 'HASH'); }

    By the way, you said "reference to a list", but there is no such thing. You meant "reference to an array".

      Thanks for the response, and yes I meant "reference to an array" ;)

      It seems to me that I left out a particularly important piece of information. All I'm really trying to do is pass through my routine to another caller and verify that I am providing the results that the caller is expecting. As both you and phaylon point out, calling the provided subroutine ref in list context is the solution. The missing piece of the puzzle is how to handle scalars.

      friedo provided the answer. If I wind up with a list of length one, I can return a scalar if my wrapper subroutine is called in scalar context as determined by wantarray.

      Thanks to all!
Re: Determining subroutine return type
by phaylon (Curate) on Feb 24, 2005 at 21:37 UTC
    First, I would save it to an @array rather than a scalar. If the element has one value (@ary == 1) it would be a scalar which you can test with ref(). The problem is more to find out whether if it's a hash or an array, because a hash is a list with an even number of elements.

    Who design's such functions? Maybe there are ways through the Devel::-Modules, but that's a bit out of my skills yet.

    Hope that helped a little,
    gr, phay

    Ordinary morality is for ordinary people. -- Aleister Crowley
Re: Determining subroutine return type
by Roy Johnson (Monsignor) on Feb 24, 2005 at 22:39 UTC
    if the subroutine returns a list I don't want to evaluate it in scalar context and end up with the length.
    You wouldn't. You'd get the last element of the list. Not that that's much preferable. If you assigned the results to a list containing one scalar, you'd get the first. You'd have to do the $bar = () = foo trick to get the length.
    my $foo = sub { return (4,5,8) }; # evaluate in (too-short) list context: my ($bar) = &$foo; print "Bar=$bar\n"; # evaluate in scalar context: $bar = &$foo; print "Bar=$bar\n"; # array context, sort of $bar = () = &$foo; print "Bar=$bar\n";

    Caution: Contents may have been coded under pressure.
      Subs returning arrays behave differently to subs returning lists. If your sub returns an array, calling it in scalar context gets you the number of elements. In list context there's no difference.
      perl -le "@a=(2,4,6); sub x{@a} print scalar(x());"
      prints 3, not 6.
Re: Determining subroutine return type
by herveus (Prior) on Feb 25, 2005 at 15:18 UTC
    Howdy!

    sub execSubroutine { my $sub = shift; my @sub_ret = &{$sub}(@_); # Return an list if we get a list with more than one value # this will satisfy callers expecting lists and/or hashes return @sub_ret if (@sub_ret > 1); my $rval = $sub_ret[0]; # If we have a ref de-reference and return. if (my $type = ref($rval)) { return @$rval if ($type eq 'ARRAY'); return %$rval if ($type eq 'HASH'); } # If not a ref and the caller wants a list, give it to them if (wantarray) { return @sub_ret; } # Otherwise return a scalar return $rval; }

    To add staples to the belt and suspenders, and to cover the chance that the sub you are calling displays different behavior in list versus scalar contexts, you could try the following:

    sub execSubroutine { my $sub = shift; my (@array, $scalar); if (wantarray) { @array = &{$sub}(@_); return @array if (@array != 1); $scalar = $array[0]; } else { $scalar = &{$sub}(@_); } my $rval = $scalar; # If we have a ref de-reference and return. if (my $type = ref($rval)) { return @$rval if ($type eq 'ARRAY'); return %$rval if ($type eq 'HASH'); } # Otherwise return a scalar return $rval; }
    This ensures that the calling context is supplied to the target.

    yours,
    Michael

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://434264]
Approved by jdtoronto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (2)
As of 2024-04-20 03:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found