Know if something is a boolean

This is a chapter in Perl New Features, a book from Perl School that you can buy on LeanPub or Amazon. Your support helps me to produce more content.


So far, Perl has not had special boolean values. Perl doesn’t even have a strictly true value; it has specified false values (0, '0', empty string, and undef), and everything that is not false is true. Not only that, by using strings or numbers (or undef) interchangeably and output stringifying everything, Perl can’t tell how it should represent the value in formats that have particular ideas about booleans, but now there’s something that can help with that.

Through builtin, v5.36 adds true and false functions and a way for you to know that a value is a boolean. This allows you to represent these values correctly for other formats.

Before you get too excited, Perl may have these features but your favorite libraries probably aren’t using them yet. Some of those have special ways to represent these, and they’ll probably aren’t going to change since they have to support the old and new ways.

A JSON example

Consider this JSON, which has true and false:

{ "updated": true, "paid": false }

Now look at what happens when you encode plain, non-special Perl values as JSON. Take the Perl true and false values from the results of some comparisons:

use v5.10;
use JSON;

my $true  = (1==1);
my $false = (0==1);

my $data = { "updated" => $true, "paid" => $false };
my $json = encode_json($data);

say $json;

The output shows that Perl created both JSON values as strings, and the false value is the empty string. Perl dumbly stringifies everything as best if can since it doesn’t know about fine-grained types:

{"updated":"1","paid":""}

This means that any external JSON parsers will not correctly find JSON’s true or false values. The string "1" is no more special than any other string:

$ echo '{"updated":"1","paid":""}' |⏎jq '. | select(.updated == true)'

With JSON’s true value, the filter works:

$ echo '{"updated":true,"paid":false}' |⏎jq '. | select(.updated == true)'
{
  "updated": true,
  "paid": false
}

Perl’s JSON module solves this by using special references so it knows that there is a boolean value. Instead of Perl’s squiggly true or false values, it uses references to either 1 or 0, which it then turns into special internal objects:

use v5.10;
use JSON;

my $true  = \1;
my $false = \0;

my $data = { "updated" => $true, "paid" => $false };
my $json = encode_json($data);

say $json;

Now the output is what JSON expects:

{"updated":true,"paid":false}

Going the other way, from JSON to a Perl structure, dumping the values shows how JSON uses special objects:

use v5.10;
use JSON;

my $json = qq({ "updated": true, "paid": false });
my $data = decode_json($json);

use Data::Dumper;
say Dumper($data);

The output shows that JSON uses a special class, JSON::PP::Boolean, where the object value is a reference to Perl’s false or non-false values:

$VAR1 = {
	'updated' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
	'paid' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' )
	};

This is one of the cures for people who want types. As long as the object can handle the behavior, you don’t care what it actually is.

true and false

Through the new builtin, Perl v5.36 adds the new functions true and false. These are “distinguished” boolean values because they are explicitly for boolean operations. You can use these anywhere you’d use any of Perl’s false or non-false values. For example, in a while conditional:

use v5.36;
use builtin qw(true false);
use experimental qw(builtin);

while( true ) {
	state $count = 0;
	say $count;
	last if $count++ > 3;
	}

Under the hood, the “distinguished” boolean values have an extra BOOL flag. In this program, the first two boolean values are the new special values and the last one is just a plain "1":

use v5.36;
use builtin qw(true false);
use experimental qw(builtin);

use Devel::Peek;

my $from_function = true;
Dump($from_function);

my $from_eq = 'a' eq 'b';
Dump($from_eq);

my $from_one = "1";
Dump($from_one);

Devel::Peek shows that the first two values have a BOOL flag. Compare those to the last dump, which does not have the boolean value:

SV = PVNV(0x7fcd72809850) at 0x7fcd74008b00
  REFCNT = 1
  FLAGS = (IOK,NOK,POK,IsCOW,pIOK,pNOK,pPOK)
  IV = 1
  NV = 1
  PV = 0x106f29ac3 "1" [BOOL PL_Yes]
  CUR = 1
  LEN = 0
SV = PVNV(0x7fcd72809990) at 0x7fcd74008a88
  REFCNT = 1
  FLAGS = (IOK,NOK,POK,IsCOW,pIOK,pNOK,pPOK)
  IV = 0
  NV = 0
  PV = 0x106f29ac5 "" [BOOL PL_No]
  CUR = 0
  LEN = 0
SV = PV(0x7fcbeb00b050) at 0x7fcbeb86e408
  REFCNT = 1
  FLAGS = (POK,IsCOW,pPOK)
  PV = 0x600003bd9930 "1"\0
  CUR = 1
  LEN = 10
  COW_REFCNT = 1

Testing for a boolean

The previous section showed that Perl now tracks a difference between distinguished booleans and the “normal” truthy values. Along with the new true and false values, builtin also provides is_bool so you can tell the difference between these new values ane “normal” values:

use v5.36;
use builtin qw(true false is_bool);
use experimental qw(builtin);

my $from_function = true;
say is_bool($from_function) ? 'boolean' : 'not boolean';

my $from_eq = 'a' eq 'b';
say is_bool($from_eq) ? 'boolean' : 'not boolean';

my $from_one = "1";
say is_bool($from_one) ? 'boolean' : 'not boolean';

The is_bool returns true for the first two, but false for the last one:

boolean
boolean
not boolean

Maintaining boolean status

If Perl can track boolean status, how would a value lose it? Or maybe, gain it? Comparisons return boolean values. Negation (and double negation) return distinguished booleans. Mathematical and string operations lose the BOOL flag. Here’s a program to demonstrate various situations:

use v5.36;
use builtin qw(true false is_bool);
use experimental qw(builtin);

sub check ( $v ) { is_bool( $v ) ? 'boolean' : 'not boolean' }

my $format = "%13s is %s\n";
printf $format, "1",             check(    1 );
printf $format, "0",             check(    0 );
printf $format, "!1",            check(  ! 1 );
printf $format, "!0",            check(  ! 0 );
printf $format, "!!1",           check( !! 1 );
printf $format, "!!0",           check( !! 0 );
printf $format, "1 == 1",        check( 1 == 1 );
printf $format, "'a' eq 'b'",    check( 'a' eq 'b' );
printf $format, "true",          check( true );
printf $format, "false",         check( false );
printf $format, "0 + true",      check( 0 + true );
printf $format, "0 + false",     check( 0 + false );
printf $format, "'' . true",     check( '' . true );
printf $format, "'' . false",    check( '' . false );
printf $format, "true == true",  check( true == true );
printf $format, "true eq false", check( true eq false );

The output shows what has the BOOL flag and what doesn’t:

			1 is not boolean
			0 is not boolean
		   !1 is boolean
		   !0 is boolean
		  !!1 is boolean
		  !!0 is boolean
	   1 == 1 is boolean
   'a' eq 'b' is boolean
		 true is boolean
		false is boolean
	 0 + true is not boolean
	0 + false is not boolean
	'' . true is not boolean
   '' . false is not boolean
 true == true is boolean
true eq false is boolean

Further reading

From the Perl documentation