Pondering the best idiom: return if this is true...

merlyn on 2005-03-05T16:52:02

I'm trying to figure out how best to write this. I want a subroutine to call another subroutine, and if the return value from that subroutine is true, this subroutine should return that value, otherwise continue. Here's some contenders:

eval { return thatroutine() || die };

for (thatroutine()) { return $_ if $_ }

$_ and return $_ for thatroutine();
And then there's a few others that create explicit vars. Any better way to say that?


Maybe returning

dws on 2005-03-05T17:03:59

Are you willing to suffer a temporary variable other than $_? If so, I would think that
return $v if my $v = thatroutine();
expresses the notion reasonably clearly, and less verbosely than
my $v = thatroutine();
if ( $v ) return $v;
or
my $v = thatroutine();
return $v if $v;
While verbose, neigher of these latter two is liable to lead a reader astray.

Re:Maybe returning

merlyn on 2005-03-05T19:32:04

return $v if my $v = thatroutine();
That won't work. The $v isn't declared early enough. There's no way to declare my $v and use it in the same statement.

Re:Maybe returning

Aristotle on 2005-03-05T22:58:46

But if you use $_ instead it will work. Along the same lines,

$_ = thatroutine() and return $_;

which IMO has a better distribution of emphasis.

And a clever for the sake of clever solution:

return $_ for grep $_, thatroutine();

Re:Maybe returning

btilly on 2005-03-06T05:13:05

$_ = thatroutine() and return $_;
is a very bad solution because it overwrites $_, which is a global variable shared across all packages which some caller far up the chain may be using.

At the very least throw a local in there.

Re:Maybe returning

Aristotle on 2005-03-06T19:03:02

I thought of that, actually, but didn't look closely enough at Randall's solutions referring to $_ to notice they merely alias – rather than overwrite – it. My bad.

last

itub on 2005-03-05T17:58:11

I generally use the simple and clear, although verbose:

my $v = thatroutine(); return $v if $v;

But another interesting idiom could be this one:

{ return thatroutine() || last }

do?

Smylers on 2005-03-05T18:42:14

Hmmm, awkward — this is one of those things that sounds like it ought to be simple (and clean) to do in Perl, but turns out not do be. I don't like the eval version, for some reason: perhaps it gives a misleading impression that you expect the called subrountine is likely to die?

How about:

return thatroutine() || do {
  # rest of the sub goes here
};

That expresses the logic of what you're up to: return the result either of that function or of running the rest of the code.

I'd probably shunt out the rest of the code to another sub just for symmetry, so you could do:

return thatroutine() || otherroutine($var, \%whatever);
Smylers

Re:do?

schwern on 2005-03-06T17:30:57

Trouble is nesting.

    sub foo { ...some code...

            return thatroutine() || do { ...some more code...

                    return thisroutine() || do { ...some more code...
                    };

            };
    }

You're already nested once for the subroutine. Then again for the return exception and then possibly again and again.

Anyhow, I think that's often the wrong emphesis. Often you're kinda aborting the routine, for example.

    sub user_info {
            my $uid = shift;

            my %info = _info_cache($uid);
            return %info if keys %info; ...get the user info...
    }

However, as you mentioned above, that might be better expressed by splitting into three routines:

    sub user_info {
            my $uid = shift;

            my %info = _info_cache($uid) || _get_user_info($uid);
            return %info;
    }

PLAIN ol' text

schwern on 2005-03-06T17:32:06

Ugg, something went wrong with the formatting on that first example. Should be newlines before all the "...some more code..."

Re:PLAIN ol' text

Aristotle on 2005-03-06T19:30:36

Try HTML formatting and using <ecode></ecode> tags. Automatically indents the code block for you too (less whitespace on copypaste).

Re:PLAIN ol' text

schwern on 2005-03-07T17:17:45

Its PLAIN TEXT!

Re:do?

Aristotle on 2005-03-06T19:29:04

No, nesting is not a problem.

sub foo {
    # ...
    return
        thatroutine()
        || do {
            # ...
        }
        || do {
            # ...
        }
        || do {
            # ...
        }
        || do {
            # ...
        }
        || do {
            # ...
        }
        || do {
            # ...
        };
}

PS.: before I added this PS, the site wouldn't let me submit this comment because there was “too much repetition.” WTF?

Re:do?

schwern on 2005-03-07T17:20:20

That code does not do the same thing as I posted. Also it seems like its just a really awful way to write an if/elsif/else.

PS use.perl's odd filters have tripped me before. I think they're vestiges from Slashdot that Pudge hasn't turned off.

Re:do?

Aristotle on 2005-03-07T22:07:38

It does do the same thing – the thisroutine() call is just the last statement of the first do {} block.

The construct doesn't have much in common with an if/elsif chain though. You could write it as one, but it would be very awkward and stilted.

Not that I'm a big proponent of this style, mind you.

eval correct?

offerk on 2005-03-06T13:11:13

Hi,
In your question you wrote:
if the return value from that subroutine is true, this subroutine should return that value, otherwise continue.
I don't see that happening with just this line:
eval { return thatroutine() || die };
The "return" will return the value of "thatroutine()" outside the "eval", but it won't cause the inclosing subroutine to return, if I read the docs correctly. What am I missing?

Anyway, I usually prefer a simple, readable solution, one that doesn't depend on the short-circuit behaviour of "&&" and relatives (not everyone groks such code), even if it is a little more verbose. How about:
# I also dislike single letter variable names ;-)
if (my $significant_name = thatroutine()) {
   return $significant_name
}

Re:eval correct?

Aristotle on 2005-03-06T19:16:18

(not everyone groks such code)

Sorry, but that's a very poor reason to avoid the short circuit behaviour of boolean ops. Programmers worth their salt must know about it, it such a basic tool, so helpful tool for improving legibility, and so widespread among languages. Without flinching I will go so far as to suggest that those who really cannot figure it out even after explanations, possibly repeated, shouldn't be touching code in the first place.

Clarity shrt

schwern on 2005-03-06T17:21:17

It would be nice if there was a clear, short idiom to do this but if one cannot be found favor clarity over size.

        my $whatever = foo();
        return $whatever if $whatever;

I know its a lot of repeating $whatever but its at least clear(ish). Contrast with the other ideas:

        eval { return thatroutine() || die };

        for (thatroutine()) { return $_ if $_ }

        $_ and return $_ for thatroutine();

They all trip mental circut breakers. eval. Loops. Uses of $_. They all say "this line is doing something complicated" which will cause the reader skimming code to stop and have to study that line. Mental speed bumps.

Additionally the above obscures the critical component, return. The most important points in a subroutine are how you can get into it and how you can get out of it. That you're return()ing should be the emphesis of the line.