What values should a boolean function in Perl return?

Krazy Glew picture Krazy Glew · Sep 17, 2016 · Viewed 10.9k times · Source

SHORT QUESTION

What are the best ways to represent true and false consistently in libraries of Perl code?

  • 1 / 0?

  • 1 / the special empty string that Perl's native boolean operators
    return?

  • undef?

  • () (i.e. the empty list)?

QUESTION BACKGROUND

We all know that Perl is very flexible with regard to booleans, as with most things.

For example, Perl treats the following as false: undef(), the number 0 (even if written as 000 or 0.0), an empty string, '0' (a string containing a single 0 digit). Perl treats the following as true: any other scalar values, 1, -1, a string with a space in it (' '), '00' multiple 0s in a string, "0\n" (a '0' followed by a newline), 'true', 'false', 'undef', etc. Plus, array-to-scalar and list-to-scalar conversions mean that you can often get away with using an empty array as a false value. (Credit to http://perlmaven.com/boolean-values-in-perl for the partial list of scalar values accepted as true or false by Perl.)

But given all of these values that are treated as true or false, what should a Perl boolean function return?

  • 1/0
  • -1/0 (nah)
  • 1/undef
  • 1/""

If you use any of these conventions, you will quickly find that the code that you write does not behave as nicely as normal Perl code. You will not be able to replace $a<$b by custom_less_than($a,$b) and have it work exactly the same.

Consider:

> perl -e 'use warnings; 
           sub my_eq { return ( $_[0]==$_[1] ? 1 : 0 ) }
           print "compare (1==0) <<".(1==0).">>"
                 ." to my_eq(1,0) <<".my_eq(1,0).">>"
                 ."\n" ;'
Output:
compare (1==0) <<>> to my_eq(1,0) <<0>>

What is the best known method for returning values from boolean functions in Perl when you are writing them yourself?

Perhaps you want your code to be Perl-like, potentially substitutable for Perl's existing boolean operators. Or perhaps you want numeric values like 1/0. Or perhaps you come from LISP, and expect Perl's undef to be used LISP's nil for false (but then you trip upon Perl's implicit treatment of many other values as false).

Answer

Dave Cross picture Dave Cross · Sep 18, 2016

I return 1 for a Boolean true value. I can't really see that !!1 adds anything other than confusion.

But I usually return nothing for a Boolean false value. That's because a bare return will return an appropriate value depending on how the subroutine has been called. The documentation for return says this:

If no EXPR is given, returns an empty list in list context, the undefined value in scalar context, and (of course) nothing at all in void context.

This is important as true and false values can differ subtly between scalar and list context. Imagine a subroutine like this:

sub some_boolean {
  if ($some_condition) {
    return 1;
  else {
    return undef; # same effect with any scalar value
  }
}

This works fine if it is called in scalar context.

if (some_boolean()) {
  ...
} else {
  ...
}

All works as expected. But if you call it in list context, things go a bit weird.

my @array = some_boolean();
if (@array) {
  ...
} else {
  ...
}

In this case, the else block is never called. In list context, your subroutine returns a list containing a single scalar (the value undef) so @array has a single element and the if (@array) test is always true.

Of course, your subroutine wasn't meant to be called in list context. But you can't control how other programmers will use your code.

But if the subroutine is written like this:

sub some_boolean {
  if ($some_condition) {
    return 1;
  } else {
    return;
  }
}

Everything will work as expected. If your subroutine is called in scalar context, it will return a scalar false value and if it is called in list context, it will return an empty list.

Returning an explicit false scalar value from a subroutine is, in my opinion, always suspect.

If you want to return an explicit scalar false value, then it's well worth checking the calling context and taking appropriate action if it's wrong.

croak 'Subroutine called in list context.' if wantarray;

Update: To answer this comment.

IIRC I tried using this approach, but gave up on it because it produced more warnings about undefined values - i.e. it was not a drop-in replacement for an existing Perl 1/!!0 comparisons, nor for 1/0. Consider perl -wle 'print "a()=<<".a().">>\n"; sub a {if(@_) {return 1} else {return}} ', versus return !!@_

The job of a Boolean value is that you can ask it if it is true or false. That is all. In my opinion, you should not expect to be able to just print a Boolean value. The fact that you often can print Boolean values without triggering warnings is nice, but shouldn't be relied on. If I wanted to print a Boolean value, I would always use some kind of logic check (probably the ternary operator) to do it. So I'd write your example like this:

$ perl -wle 'print "a()=<<".(a() ? "True" : "False").">>\n"; sub a {if(@_) {return 1} else {return}}'

I'll just reiterate my original point. If you are returning any scalar value for false and there is any way that your subroutine can be called in list context (even accidentally) then you have introduced a potential bug into your code.

That, alone, is worth the trivial pain of having to decode Boolean values before printing them.