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

My application has a longish subroutine ('getinfo' in my example) that determines many values in one process. However, not all those values are used at once by the same calling routines, but only single values are used by various routines throughout the script.

I really don't want to duplicate the longish subroutine to just return the one value for a particular instance--I'd like to just have it written once and return all the values and then pick and choose the value I need once returned to the calling subroutine. But it seems inefficient and sloppy. Is there a better way? Here's a very simple example. Thanks.

sub routine_A { my ($name, $address) = getinfo(); print $name; } sub routine_B { my ($name, $address) = getinfo(); print $address; } sub getinfo { my $name = "Sam"; my $address = "123 Main St"; return ($name, $address); }

"The important work of moving the world forward does not wait to be done by perfect men." George Eliot

Replies are listed 'Best First'.
Re: More efficient return of values from sub
by tlm (Prior) on Mar 26, 2005 at 04:32 UTC

    This is a very generic problem, and every solution I can think of is one variant or another of caching. The only issue is to decide how to implement this caching. Memoization gives this task to the routine itself. E.g.:

    { my %cache; sub heavy_lifting { my $arg = shift; return defined $cache{ $arg } ? $cache{ $arg } : $cache{ $arg } = grunt($arg); }
    (BTW, I recently posted a simple example of memoization here.) Or if the routine is an object method, the caching could be done by each instance:
    sub heavy_lifting { my $self = shift; my $arg = shift; return defined $self->{ _heavy } ? $self->{ _heavy } : $self->{ _heavy } = $self->grunt( + $arg ); }
    Or as dragonchild says, you can let the calling code worry about caching the results. Caching is everywhere, in many guises. It is at the heart of three popular sorting optimization techniques, the Orcish Maneuver, the Schwartzian Transform, and the Guttman-Rosler Transform, for example (although one doesn't usually associate these techniques with caching per se). And of course it is done constantly by your computer's memory hardware, but that's getting away from Perl. Etc., etc. This barely scratches the surface on the uses and strategies for caching. There are entire modules devoted to various forms of caching (e.g. the very useful Cache::FileCache, and of course Memoize).

    the lowliest monk

Re: More efficient return of values from sub
by cazz (Pilgrim) on Mar 26, 2005 at 02:25 UTC
    If you are concerned that getinfo() takes too long, a sprinkling of Memoize could be useful.
Re: More efficient return of values from sub
by moot (Chaplain) on Mar 26, 2005 at 02:36 UTC
    ..and if Memoize won't work in your particular instance (returning values from a database, for example), you could perhaps call getinfo() outside of your routines A & B:
    my ($name, $address, $other, $info, $here) = getinfo(); ... sub routine_A { ... print $name; } ...
    or, depending on your architecture, store the values temporarily somewhere, perhaps in an instance variable or similar. For example if you don't know/ care which of routines A & B get called first, something like this might be helpful (which does unfortunately use a lexical global):
    my @vars; sub routine_A { ... if (!@vars) { @vars = getinfo() } my $name = $vars[0]; print $name; } sub routine_B { ... if (!@vars) { @vars = getinfo() } my $address = $vars[1]; print $address; }
    That way you're only calling getinfo() once, and then only if needed. Really, your chosen solution will depend a lot on whether you have access to the internals of getinfo(), whether getinfo() can be memoized, and the architecture surrounding these few functions.

    Update: (still coming off the meds). Of course this sort of caching is what Memoize is for.. I wrote this under the impression that getinfo() might be called elsewhere in the code, not just directly in this subset of functions. Essentially though you'll want to cache the returned values somewhere outside of the routines.

Re: More efficient return of values from sub
by fglock (Vicar) on Mar 26, 2005 at 03:12 UTC
    sub routine_A { my ($name) = getinfo('name'); print $name; } sub routine_B { my ($address) = getinfo('address'); print $address; } sub getinfo { my %data; $data{name} = "Sam"; $data{address} = "123 Main St"; return @data{ @_ }; } routine_A; routine_B; print getinfo( 'name', 'address' );

      Perfect fglock! I actually wondered about hashes for a fleeting second after posting, but this has helped me see the logic and application of this approach. It keeps it 1) simple, 2) no outside modules, and 3) no global variables (one of my early 'solutions').

      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re: More efficient return of values from sub
by dragonchild (Archbishop) on Mar 26, 2005 at 03:42 UTC
    Has there been a problem with some resource(s) that causes you to look at this subroutine? If there hasn't, then why are you worrying? Just return an hashref and let the caller worry about it. Or, return an singleton object with a nice interface and let the object lazy-load what it gets asked for. (This is a really good use of the Singleton pattern, in case you're wondering.)

    Now, if you're having performance problems, have you profiled your app to find where the problems are? I would be very surprised if it was the returning semantics that was causing you performance problems.

    So, just bundle it up into a Singleton, clean up your callers, and go home. :-)

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: More efficient return of values from sub
by jbrugger (Parson) on Mar 26, 2005 at 09:54 UTC
    I would use oo to solve this, an object keeps all the vals for you that you need, and you can do lots more with them as you pass on.
    #!/usr/bin/perl use strict; my $obj = blah->new(); sub A() { print( $obj->getName . " , " . $obj->{address} . "\n" ); } sub B() { print( $obj->getName . " , " . $obj->{address} . "\n" ); } A; B; package blah; use strict; sub new() { my $classname=shift; my $self=bless{}; $self->{name} = "Sam"; $self->{address} = "123 Main st"; return $self; } sub getName() { my $self=shift; if ($self->{name}) { return $self->{name}; } else { #Init name and return value... } } 1;
    "We all agree on the necessity of compromise. We just can't agree on when it's necessary to compromise." - Larry Wall.
Re: More efficient return of values from sub
by gam3 (Curate) on Mar 27, 2005 at 00:52 UTC
    I would say that getinfo should be an object, then you can partion the work easily latter. However here is a simple solution.
    sub NAME { 0 }; sub ADDRESS { 1 }; sub routine_A { print getinfo()->[NAME], "\n"; } sub routine_B { print getinfo()->[ADDRESS], "\n"; } sub getinfo { my $name = "Sam"; my $address = "123 Main St"; return [$name, $address]; }
    -- gam3
    A picture is worth a thousand words, but takes 200K.
Re: More efficient return of values from sub
by Limbic~Region (Chancellor) on Mar 27, 2005 at 17:26 UTC
    Your getinfo example doesn't reflect what expensive operations you are trying to avoid nor have you indicated what level of visibility (scope) you need. To summarize the examples provided so far:
    • Memoization: The sub is only invoked once per unique set of arguments while subsequent invocations hit cache
    • Raise Scope: The return values are elevated in scope so that they can be seen by all subs in the package. This can be simplified by storing in a hash
    • Convert Return Values To Hash: This is the solution from fglock that you appear to like. This only simplifies the retrieval process, you are still building the hash with whatever expensive operations you have each time it is called
    • Hashref, Singleton, Object: These are all variations on item 2. Depending on implementation you can defer expensive operation until requested

    Without knowing more information, I would make a few assumptions and not have a getinfo() sub at all.

    #package declaration if applicable my %info = ( name => expensive_op1(), address => expensive_op2(), ); # later on in the code sub routine_A { print $info{name}; }

    Cheers - L~R