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

I wrote a subroutine that returns a string when it succeeds, but a negative integer when it "fails". The integer changes depending on which failure occurred. My question: is there a better way to check if the sub failed than what I have below (ofc without throwing a warning)?
use strict; use warnings; for(my $ii = 1; $ii < 4; $ii++) { my $a = foo($ii); if($a =~ /\d/) { print "Fail occured with code: $a\n"; } else { print "Success\n"; } } sub foo { my $num = shift; if($num == 1) { return -1; } elsif($num == 2) { return -2; } else { return "true\n"; } }

Replies are listed 'Best First'.
Re: return from subfunction
by jdporter (Paladin) on Apr 07, 2011 at 19:24 UTC

    In such cases, I usually prefer to use exceptions, i.e. die / eval.

    sub foo { open F, ... or die "-1\nerror opening file"; return $result; } my $string; eval { $string = foo(); }; if ( $@ ) { ( my $code, $string ) = split /\n/, $@; ... }

    If you really don't want to go that route, then you could return a return "object" which contains multiple fields. (It doesn't have to be a blessed object, though.)

    sub foo { open F, ... or return( [ -1, "error opening file" ] ); return( [ 0, $result ] ); } my( $code, $string ) = foo();

    Lastly: realize that die can return an object, not just a string. This can be pretty useful in cases such as this.

    sub foo { open F, ... or die [ -1, "error opening file" ]; return $result; } my $string; eval { $string = foo(); }; if ( $@ ) { ( my $code, $string ) = $@; ... }
    I reckon we are the only monastery ever to have a dungeon stuffed with 16,000 zombies.
      Perfect! Thank you.
Re: return from subfunction
by Eliya (Vicar) on Apr 07, 2011 at 19:29 UTC

    You could make your "success" return value(s) dual-type (see dualvar) with the numeric part being zero, in which case you could simply test if the value is less than zero:

    use strict; use warnings; use Scalar::Util 'dualvar'; for(my $ii = 1; $ii < 4; $ii++) { my $a = foo($ii); if($a < 0) # <-- { print "Fail occured with code: $a\n"; } else { print "Success\n"; } } sub foo { my $num = shift; if($num == 1) { return -1; } elsif($num == 2) { return -2; } else { return dualvar 0, "true\n"; # <-- } }

    The main purpose of this is that the code would run cleanly under strictures, i.e. without eliciting the warning 'Argument "true\n" isn't numeric in numeric lt (<)'.

    In other words, your original regular scalars would in principle work, too (a string which doesn't look like a number, evaluates to zero), but you'd have to say no warnings 'numeric'; — which ultimately could get even more unwieldy than what you have now, in case you try to restrict it to the minimal scope required every time...

      I'll add dualvar to the list of things for me to look into. Thanks!
Re: return from subfunction
by BrowserUk (Patriarch) on Apr 07, 2011 at 18:43 UTC

    In the real code, what will you do when you detect a failure?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Depends. This code is in my "Common.pm" module which I keep common subroutines I write. So anything under the sun I suppose.

      If it helps answer your question, the subroutine takes in a folder and a file. It returns various statuses depending on if the folder is writeable, readable, the combined path is used, not used... you get the idea.
        It returns various statuses depending on if the folder is writeable, readable, the combined path is used, not used... you get the idea.

        So the negative failure values mean readable or writable etc? What does the successful string represent?


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: return from subfunction (list assignment rules)
by LanX (Saint) on Apr 07, 2011 at 21:59 UTC
    I think your case is well defined, as long as your strings don't look like a negative number. (you should change your regex to /^-\d$/).

    But for a general pattern I prefer to flag error condition with an empty list, and to use a list assignment. Like this you can still return undef or an empty string in a boolean context (thats especially good for iterators)

    (see semipredicate problem)

    You can use one of the special vars for errors ($! $@ ...) to tunnel the message or just define your own special var in a dedicated namespace like $Error::message. (like this you're free to add as many informations as you want)

    for (1..4){ print "$_: "; if ( ($str) = func($_) ) { # list assignment only false for empty li +st print "Success $str\n"; } else { print "Error $Error::message"; } } sub func { $Error::message="Wrong wrong wrong!!"; goto "L".shift; L1: return ""; L2: return 0; L3: return undef; L4: return; # blank returns empty list } __DATA__ 1: Success 2: Success 0 Use of uninitialized value $str in concatenation (.) or string at /tmp +/tst2.pl line 4. 3: Success 4: Error Wrong wrong wrong!!

    Frankly I don't understand this culture to rely on returning false scalars...

    Of course jdporters pattern with exceptions is slightly cleaner, but this pattern fits better into the standard pattern to simultaniously execute and check within a boolean context (if,unless,while,until, and,...)

    Cheers Rolf

    UPDATE: extended code example

Re: return from subfunction
by Voronich (Hermit) on Apr 07, 2011 at 19:09 UTC
    Is there any chance the string return could parse as a negative number? I'd say start by just doing the compares using 'eq' (i.e. if ($return eq '-1')), though I expect that might make people cringe in 3....2....1...
    Me
      Because it's in my common module, I want to keep the possibility open that I might add in more statuses. Hence the failure check would have to be any negative number, not specific ones. This is why I went with regex. Ofc I note that my regex matches any number in the example, but in my real code, I address that.

      And yes, the success string will never be just a number. At the very least even if "" is passed as the folder and -1 is passed as the number and it passes all checks, the sub would return "/-1"
        Then what you have is pretty good. Assuming you've got a reliable branch based on error code (which you appear to), you could then use that as a dispatch so that the processing function itself had no knowledge of the errors themselves. Just go one step farther and take the error code (once you know that's what you've got) and pass it in to some error-handling function which either knows, or has access to a table of some kind that knows how to handle individual errors. That keeps your driver function sufficiently generic that it could handle anything as long as "errors are always negative numbers."
        Me

        This sort of “specialized knowledge” about the return-codes should be encapsulated within a sub within that package.   If the caller wants to know, “is this an error?”, then it should be able to get the answer by calling a sub which answers that question, yes or no.   Because, someday, that’s gonna change, and when it does, you do not want to be grepping through the source-folder, finding 100 occurrences of logic that must be changed, and changing (oops...) 99 of them.

        Code defensively.   If a call occurs in an “impossible” situation, check for that situation, and (Carp) confess the sin.   The best – the only – agent that can detect such bugs is the computer itself.   Nothing is more “inefficient” than a bug, and the time spent trying to find it.

Re: return from subfunction
by locked_user sundialsvc4 (Abbot) on Apr 07, 2011 at 23:31 UTC

    I would echo the sentiment that ... “this idea stinketh .. large.”   :-D

    What I mean is ... sure ... TMTOWTDI™ ... but ... you will surely and sorely regret having done it that way.   (IMHO... YMMV.)

    I have come to realize that the best way to handle failures is to throw an exception, either with die or with one of the Carp routines.   Arbitrarily complex code can now be surrounded by eval blocks, and, no matter exactly where-or-when the failure occurs within that block, it is caught and can be dealt with – as “the exception” that it really is.   (Otherwise, you wind up with code that sounds like that annoying cell-phone commercial in the US... constantly asking,   “can you hear me now?   can you hear me now?”)

    In any case, I abhor the idea of returning a string in one case and a number in another.   Consider the idea of having a Perl class, which simply indicates success or failure in the initial call, and which then provides other methods to retrieve either the results or the error-information.   Although the idea may feel a little bit peculiar at first, I think it really works out well.   Even a simple package-variable can be used in simpler situations, with a package subroutine that retrieves the (otherwise hidden) variable’s value for you.