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?
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?
In my opinion, as a dynamic programming language, it is reasonable that Perl 6 make the “:D” optionally, but not default.