Restrain return values

The utility of Perl 6 (or any language) is something that I can’t already do. When people ask me if they should use Perl 6 (or even
regular Perl), I ask them if they are having trouble getting what they want out of their current tools.

Looking forward to Perl 6, I see many features that I’ve wished for in other languages, or that other languages have but Perl doesn’t. That’s always the problem. Perl has most of the stuff I want, but some other language has a feature I want. Moving to that other language usually means I lose the stuff Perl had.

Here’s an interesting bit from Perl 6, which has subroutine signatures (and, so does Perl 5 now). That’s fine. But Perl 6 can also enforce return values.

Consider this subroutine that returns different values based on the even-ness of the number I pass it:

sub is-even(Int $number) {
	return 0 if $number % 2;
	return 1;
	}

say is-even( 137 );
say is-even( 2 );

I get back what I’ve been told—and what I’ve told others—is true or false:

$ perl6 is_excellent.p6
0
1

Clearly, I’ve been programming Perl 5 for a long time because I use the definite values 1 and 0 to represent true and false.

But Perl 6 has the Bool class (well, enum) with True and False, so I should use those. One of the main benefits of object-oriented programming is object identity where the thingy knows what it is. A Bool knows that it’s a Boolean thingy, but we have to have magical knowledge to know what a plain 0 or 1. This isn’t special to Perl 6; many other fine languages have it.

I fix up my Perl 5-ish code to look a little more Perl 6-y (but not all the way just yet):

sub is-even(Int $number) {
	return False if $number % 2;
	return 1;
	}

say is-even( 137 );
say is-even( 2 );

Now I can get a Bool value back in one case:

$ perl6 is_excellent.p6
False
1

I don’t get a Bool in the second case. I meant to fix up that case, but I made an incomplete patch. That might be rare in the universe of computer programming, but it does happen.

But Perl 6 can enforce a return value type, although at runtime. I add to the subroutine signature:

sub is-even(Int $number) returns Bool {
	return False if $number % 2;
	return 1;
	}

say is-even( 137 );
say is-even( 2 );

Now when I run this, I get an error for the case I didn’t fix:

False
Type check failed for return value; expected Bool but got Int

There are many ways to fix this and I’ll try a wrong one before I get to the right one. First, I can turn that 1 into a Bool with a ?operator:

sub is-even(Int $number) returns Bool {
	return False if $number % 2;
	return ?1;
	}

That’s a prefix operator, meaning it comes before the single value it operates on. It turns any sort of thing into either True or False. Now my code runs without an error.

$ perl6 is-even.p6
False
True

But, I still have some problems. I may think I’m forced to return a Boolean value, but I’m not. I can return a Bool type instead:

sub is-even(Int $number) returns Bool {
	return False if $number % 2;
	return Bool;
	}

say is-even( 137 );
say is-even( 2 );

I don’t get an error, but I don’t get the right answer either. I correctly get False for the first case, but I incorrectly get back a type for the second case with no error:

$ perl6 is_excellent.p6
False
(Bool)

I’m sure we’re going to have fun with a dynamic language that passes types around, but not here. I need a better constraint on the return value.

By specifying returns Bool, I allow is-even to return definite and undefined values as long as they are from Bool. Curious! I can have object identity without a concrete value! The type object by itself has the right type even though it doesn’t have a defined value. See Day 02 – The humble type object in the 2013 Perl 6 Advent Calendar.

To force my subroutine to return a defined value, I can add a modifier to the return constraint. The :D (I hope your viewer doesn’t turn colon-D into an emoji) marks a definite value:

sub is-even(Int $number) returns Bool:D {
	return False if $number % 2;
	return Bool;
	}

say is-even( 137 );
say is-even( 2 );

Now it fails, but with a cryptic error message that say it got what it expected:

$ perl6 is_excellent.p6
False
Type check failed for return value; expected Bool but got Bool

I need to return definite values in each case:

sub is-even(Int $number) returns Bool:D {
	return False if $number % 2;
	return True;
	}

say is-even( 137 );
say is-even( 2 );

This isn’t the only way to specify the contraint, athough I think I like that one best. I can also use the arrow notation in the signature:

sub is-even(Int $number --> Bool:D) {
	return False if $number % 2;
	return True;
	}

Lastly, even though I’m writing about return values, I can use the same modifier to fix up my problem with the parameter. I specify that I expect an Int, but that can be definite or indefinite as well:

sub is-even(Int $number --> Bool:D) {
	return False if $number % 2;
	return True;
	}

say is-even( 137 );
say is-even( Int );

If I try the modulus operator % with an indefinite value, my code blows up:

False
Invocant requires an instance of type Int, but a type object was passed.  Did you forget a .new?

I fix that by specifying that I want a definite value for the argument:

sub is-even(Int:D $number --> Bool:D) {
	return False if $number % 2;
	return True;
	}

say is-even( 137 );
say is-even( Int );

Now I get an error that relates to the argument I pass. Notice what a nice error message it is:

False
Parameter '$number' requires an instance of type Int, but a type object was passed.  Did you forget a .new?

2 comments

  1. Don’t you think we’ll be plaguing our functions with :D, which seems to be the default desired behavior that we would expect when saying Int or Bool?

    1. In my opinion, as a dynamic programming language, it is reasonable that Perl 6 make the “:D” optionally, but not default.

Leave a Reply

Your email address will not be published. Required fields are marked *