Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

looking for a good idiom: return this if this is true

by merlyn (Sage)
on Mar 05, 2005 at 16:53 UTC ( [id://436920]=perlquestion: print w/replies, xml ) Need Help??

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

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?

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

Replies are listed 'Best First'.
For clarity and cleanliness, I'd recommend...
by TheDamian (Vicar) on Mar 05, 2005 at 19:31 UTC
    if (my $result = thatroutine()) { return $result; }

        This works fine, and does not clobber $_.

        #!/usr/bin/perl -w use strict; print test(), $/; sub subtest { return 4 } sub test { return $_ if ( local $_ = subtest() ); # continue return 'false'; }

      Given how often I hear about (even experienced) programmers mistakenly typing "=" in an if statement when they meant "==", I don't know if encouraging this particular idiom as a rare case where it is indeed what you mean is a good idea. Might I suggest this instead:

      while (my $result = thatroutine()) { return $result; }

      As an aside, I was actually surprised that the version with if didn't warn about the single "=", but a little testing showed that the warning only appears to happen if you mistakenly assign (instead of compare) a literal value, not a subroutine.

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        I agree with you. I would certainly never recommend that people write:
        if ($result = thatroutine()) { return $result; }
        But, as your while-based variant shows, non-constant assignments in a conditional are always fine if you're declaring the lvalue variable at that point:
        if (my $result = thatroutine()) { return $result; }
        In such cases, the = must be intentional, since == on a newly-created variable doesn't make any sense.

        As for your alternative suggestion, I don't feel it's a clear or as clean as using an if, because the use of a while implies looping behaviour, which the return undermines in a not-entirely-obvious way. That could be especially misleading to subsequent readers of the code if the number of statements in the while block later increases, pushing the return away from the loop keyword it's subverting.

Re: looking for a good idiom: return this if this is true
by dws (Chancellor) on Mar 05, 2005 at 17:15 UTC

    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.

    I'd think that keeping value retrieval and flow of control separate will be easiest on whoever picks up the code next. That speaks for either of your last two solutions, or something more explicit, like

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

    Since you're willing to consider taking on the overhead of eval, I rather suspect a temporary variable won't faze you, especially since it offers a convenient debug point for anyone who is stepping through the code.

        Huh? Are you confusing ...

        No confusion here. The bookkeeping overhead (pushing a marker onto the return stack, though I recall eval doing a bit more than that) is on par with creating a temporary variable. In my mind, at least, that cancels any performance objections about using a temporary variable.

        eval'ing a string would be nuts, and I give you credit for not being that nuts.

      why not just:

      retrun thatroutine() if thatroutine()

      edit: or

       if (thatroutine()) {return $_}

      --
      Linux, sci-fi, and Nat Torkington, all at Penguicon 3.0
      perl -e 'print(map(chr,(0x4a,0x41,0x50,0x48,0xa)))'
        retrun thatroutine() if thatroutine()
        That calls thatroutine() twice, which will perform things twice. Not good.
        if (thatroutine()) {return $_}
        $_ isn't getting set there. Are you expecting the variable to be psychic?

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

Re: looking for a good idiom: return this if this is true
by BrowserUk (Patriarch) on Mar 05, 2005 at 17:03 UTC

    T'aint pretty.

    ... { return do{ thatroutine() or last } } ...

    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
      You don't need the do:

      { return thatroutine() || last }

Re: looking for a good idiom: return this if this is true
by borisz (Canon) on Mar 05, 2005 at 17:46 UTC
    I have no better idea than the other monks so far, but I wonder how
    eval { return thatroutine() || die };
    is supposed to work, since the return does not return from the sub surounding the eval.
    Boris

      The || operator is the high priority logical or, so if thatroutine() evaluates false, die is evaluated before return is.

      Update: Oops, I misunderstood your objection, which is correct:

      $ perl -Mstrict -we'sub bar {"baz"} sub foo {eval{return bar() || die} +; "quux"} print foo()' quux$
      If the eval return was effective in the foo() call, should have printed "baz".

      After Compline,
      Zaxo

        Thats clear to me, but I do not understand the other part: The question is how to return a value, if it is true and continue otherwise.
        sub that { 5 } sub xx{ eval { return that() || die }; 1 } print xx; __OUTPUT__ 1
        But the op wants 5 and that the part outside the eval is not executed expect when that is false.
        Boris
Re: looking for a good idiom: return this if this is true
by ikegami (Patriarch) on Mar 05, 2005 at 17:07 UTC

    Not really any better, but there is:

    $rv = thatsubroutine() and return $rv;
Re: looking for a good idiom: return this if this is true
by ambrus (Abbot) on Mar 05, 2005 at 17:33 UTC

    My guesses are:

    sub thisoutine { PRE_CODE(); thisroutine() and do { CONTINUE(); }; }
    or
    sub thisroutine { PRE_CODE(); unless (thatroutine()) { CONTINUE(); } }
    However, with the latter one, you should be aware of a certain optimization bug described in Re^3: A cleaner way of scoping variables (Update: fixed link) which may cause that the return value of thatroutine is not returned it thatroutine() is a compile-time constant. Also, do not replace unless ( with if (! or if (not, as those would cause the subroutine to return the result of !thatroutine() if thatroutine returns true.
Re: looking for a good idiom: return this if this is true
by perrin (Chancellor) on Mar 05, 2005 at 18:29 UTC
    Those all look a lot more confusing than using explicit vars to me.
Re: looking for a good idiom: return this if this is true
by jdalbec (Deacon) on Mar 05, 2005 at 17:21 UTC
    return $_ if $_ = thatroutine();?

    I tried

    return $var if my $var = thatroutine();
    and
    return my $var if $var = thatroutine();
    but neither works.

      Don't do that; $_ is a global.
        I concur.

        And I've previously found when I was doing a micro-optimisation effort somewhere, that $_ is also relatively slow to assign and then read from, compared to the temporary variable.
Re: looking for a good idiom: return this if this is true
by mstone (Deacon) on Mar 05, 2005 at 22:08 UTC

    First of all, it helps to characterize the problem: you're building a very simple state machine. Each called subroutine is a new state, and the return value from each called function is the input which selects the next transition. In this case, you want FALSE to trigger a transition to the next state, and TRUE to trigger a transition to the accept state:

    (s0)- F ->(s1)- F ->(s2)-[...] | | | +---------+---------+--[...]- T ->(accept)

    In this case, you can implement that with a list of function pointers and a loop:

    sub outer { my @funcs = (\&f1, \&f2, \&f3, ...); my $result = 0; for $f (@funcs) { $result = $f->(); last if $result; } return ($result); }

    And more generally, you can use a hash as a transition table:

    sub outer { %trans = _get_transition_table(); my $f = $trans{'start'}; my $result = 0; do { $result = $f1->(); my $state = ($result) ? "$f.true" : "$f.false"; $f = $trans{ $state }; } while ($f); return ($result); } sub _get_transition_table { my $f1 = \&f1; my $f2 = \&f2; my $f3 = \&f3; [...] return ( "start" => $f1, "$f1.true" => 0, "$f1.false" => $f2, [...] ); }

    which allows for multiple output values per function, and state diagrams that are more complicated than just a simple list.

    If you want to pass parameters to the functions, pass a listref and have the called functions and use it as an argument list, a stack, a queue, or whatever:

    sub caller { [yadda yadda] my $args = []; do { $result = $f->($args); [yadda yadda] } while ($f); return ($result); } sub as_list { my $args = shift; my ($arg1, $arg2, $arg3) = @$args; @$args = (); [...] } sub as_stack { my $args = shift; my $item = pop @$args; [...] push @args, $result; return ([true|false]); } sub as_queue { my $args = shift; my $item = shift @$args; [...] push @args, $result; return ([true|false]); }
Re: looking for a good idiom: return this if this is true
by jdporter (Paladin) on Mar 05, 2005 at 18:25 UTC
    thatroutine() =~ /(.+)/s and return $1;
    Obviously, whether or not this technique will work depends on the kinds of values the function returns.

      Sorry, no offense, but this looks really terrible to me... Why on earth do you want to fire up a regex matching here?

      Probably this has also an interesting side effect: it could convert a "numeric" scalar into a "string" scalar. No good.

        I'm not saying it's good, but it does answer the OP, somewhat, and it's very short.
        ...it could convert a "numeric" scalar into a "string" scalar. No good.
        If you think such conversions are inherently bad, then Perl probably isn't the language for you.
Re: looking for a good idiom: return this if this is true
by Limbic~Region (Chancellor) on Mar 06, 2005 at 00:13 UTC
    merlyn,
    Since you already have a solution you are happy with, I thought I would throw in map in a void context for fun.
    map { $_ ? return $_ : () } that_routine();

    Cheers - L~R

Re: looking for a good idiom: return this if this is true
by Roy Johnson (Monsignor) on Mar 05, 2005 at 23:48 UTC
    This isn't really a recommended solution, but it was amusingly oblique, and correct.
    return $_ for grep $_, thatroutine();
    Update: a similar approach, slightly more concise:
    return $_ for thatroutine() or ();

    Caution: Contents may have been coded under pressure.
Re: looking for a good idiom: return this if this is true
by bart (Canon) on Mar 06, 2005 at 05:45 UTC
    I assume the intention is to continue with the rest of the sub if it's false. You can't apply it always, but somethimes you can wrap the rest of the sub in a do block:
    return thatroutine() || do { # the rest of the sub ... };

    This won't work if, for example, the return statement is to be used in the middle of a loop, or some other control flow block.

    update: I just noticed somebody on use.perl.org thought of the same solution.

Re: looking for a good idiom: return this if this is true
by TedPride (Priest) on Mar 05, 2005 at 19:07 UTC
    The examples given here seem massively overcomplicated. Try the following:
    print testing(); sub testing { return $_ if $_ = rval0(); return $_ if $_ = rval1(); } sub rval1 { return 1; } sub rval0 { return 0; }
      Except that those clobber a regional $_, which may be needed for other things. I might as well use the predeclared $a or $b instead, with just about as much chance of collision.

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

Re: Ternary?
by Anonymous Monk on Mar 05, 2005 at 18:25 UTC
    I'm not sure I understand the task, but, perhaps:  $_ = thatroutine() ? return $_ : undef;

    Gyan Kapur

      I guess you're missing a set of parens there:

      ($_ = thatroutine()) ? return $_ : undef;
      $_ ? return $_ : undef for thatroutine(); #or simply return $_ if $_ for thatroutine();

      Gyan Kapur
Re: looking for a good idiom: return this if this is true
by bageler (Hermit) on Mar 06, 2005 at 08:12 UTC
    it's an abuse of map, but it's concise:
    map{$_&&return$_}thatroutine();
Re: looking for a good idiom: return this if this is true
by nothingmuch (Priest) on Mar 07, 2005 at 00:18 UTC
    A while ago I was going to write Return::Hyper as an excercise for learning perlguts. I never got there.

    With return($level, $thingy) you get

    sub return_if { if ($_[0]){ return(2, $_[0]); } }
    Maybe someone with more, err, mojo can do it?
    -nuffin
    zz zZ Z Z #!perl
Re: looking for a good idiom: return this if this is true
by gam3 (Curate) on Mar 30, 2005 at 18:16 UTC
    I thought it might be handy to have a benchmark on some of the contenders.

    return $_ || last for subroutine() (T04) was my choice for syntax, and I was sorry to see that the for loops are so slow.

    The winner was if (my $ret = subroutine()) { return $ret)} as might be expected, as this is (I would assume) the most common way to test a return value.

           Rate  T11  T02  T10  T03  T01  T04  T09  T08  T06  T07  T05
    T11 37594/s   --  -8% -12% -12% -29% -31% -39% -39% -47% -47% -50%
    T02 40959/s   9%   --  -4%  -4% -23% -24% -33% -33% -42% -42% -46%
    T10 42581/s  13%   4%   --  -0% -20% -21% -30% -30% -40% -40% -44%
    T03 42708/s  14%   4%   0%   -- -20% -21% -30% -30% -39% -39% -44%
    T01 53095/s  41%  30%  25%  24%   --  -2% -13% -13% -25% -25% -30%
    T04 54097/s  44%  32%  27%  27%   2%   -- -12% -12% -23% -23% -29%
    T09 61265/s  63%  50%  44%  43%  15%  13%   --   0% -13% -13% -19%
    T08 61265/s  63%  50%  44%  43%  15%  13%   0%   -- -13% -13% -19%
    T06 70447/s  87%  72%  65%  65%  33%  30%  15%  15%   --   0%  -7%
    T07 70447/s  87%  72%  65%  65%  33%  30%  15%  15%   0%   --  -7%
    T05 75918/s 102%  85%  78%  78%  43%  40%  24%  24%   8%   8%   --
    
Re: looking for a good idiom: return this if this is true
by Dr. Mu (Hermit) on Mar 06, 2005 at 19:00 UTC
    The fact that this question has to be asked at all and the contortions necessary to answer it points to a deficiency in Perl's semantics. The following code
    thatroutine() and return;
    would be simple and effective were it not for the fact that a return with no argurments is equivalent to return undef (or return () in list context). By making the undef or () mandatory in order to clear the "last expression evaluated" this whole discussion could have been avoided.
      Hardly a deficiancy... especially when return; doesn't return undef _OR_ (), it returns both undef _AND_ () at the same time (dependending on context of course).

      We would have to replace all existing return; with return wantarray ? () : undef;

      ugh...
      Doesn't the following bit of code work? It's only slightly more obtuse than what you propose.
      thatroutine() and return 1;
      UPDATE: I just tested this to prevent ignorance, and it does in fact work.
      UPDATE AGAIN: Ah, I missed the point. My code does not return the value returned by thatroutine(). If there are no side effects I suppose you could do:
      thatroutine() and return thatroutine();
      :-)
      ----
      My mission: To boldy split infinitives that have never been split before!
      This reminds me of how Applescript does it. When a sub is called in void context, the variable result gets assigned the return value. Sort of like this (pseudo):
      thatroutine() and return @result;
      Sounds like a Perl 6 feature request...
Re: looking for a good idiom: return this if this is true
by mr_mischief (Monsignor) on Mar 11, 2005 at 07:41 UTC
    Probably not the "best", but I have one that make somewhat clever use of its placement in a subroutine to work in one line without creating a lexical. I'll put here the whole test program.
    #!/usr/bin/perl -w use strict; my $foo = shift; sub one { return shift; } sub two { push @_, one( $foo ) and $_[0] ? return pop : pop; return "No arg"; } print two() . "\n";;
    Maybe useful if you're looking for some obfuscation. It works with unshift/shift as well, of course.


    Christopher E. Stith
Re: looking for a good idiom: return this if this is true
by redlemon (Hermit) on Mar 10, 2005 at 13:47 UTC

    if you wouldn't mind refactoring thatroutine, this works:

    sub thatroutine(\$) { # do something to ${$_[0]} } sub thisroutine { return $_ if thatroutine local $_; ... my $namedvar; return $namedvar if thatroutine $namedvar; }
    Ugly as hell but it appears to work.

Re: looking for a good idiom: return this if this is true
by tlm (Prior) on Mar 23, 2005 at 10:55 UTC

    I haven't seen this among the replies, so here it goes

    $_ = thatroutine() and return $_;
    It's very similar to the already-mentioned
    return $_ if $_ = thatroutine();
    I like both roughly equally.

    the lowliest monk

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (4)
As of 2024-04-23 20:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found