Among dozens of other key Perl 6 features, I consider Signatures one of the many ‘killer’ features. They are so full of functionality and so powerful, I suspect a whole book could be written on how to use them well. I want to explore one specific bit of functionality I originally overlooked, but I’ve come to cherish.
You’ve probably seen the basic subroutine signatures:
sub myfunc($x, $y, $z) {...}
This declares 3 scalar parameters for the function, and gives them the names $x, $y, and $z within the body of the function.
Simple stuff.
You can get fancier, giving them specific types:
sub myfunc(Str $x, Int $y, Rat $z) {...}
You can use smilies to require the values to be defined:
sub myfunc(Str:D $x, Int:D $y, Rat:D $z) {...}
There are many other fancy specifiers you can use I won’t get into here.
But what if your parameter is more complex? (NOT Complex – though that works too..)
For example, you might want to restrict a specific parameter to a Positional argument like an Array, or an Associative one like a Hash using the respective sigils, @ or %.
sub myfunc(%h) {...}
Now I can call the function with a hash:
myfunc(%( a => 1, b => 'this', c => 2.2));
If I want to verify that those specific fields are present, I can put code in the top of my function to do that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sub myfunc(%h) { | |
die "a must be an Int" unless %h<a> ~~ Int; | |
die "b must be a Str" unless %h<b> ~~ Str; | |
die "c must be a Rat" unless %h<c> ~~ Rat; | |
} |
If I want to also simplify the way I refer to those fields, I can assign them to other variables:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sub myfunc(%h) { | |
die "a must be an Int" unless %h<a> ~~ Int; | |
die "b must be a Str" unless %h<b> ~~ Str; | |
die "c must be a Rat" unless %h<c> ~~ Rat; | |
my $a = %h<a>; | |
my $b = %h<b>; | |
my $c = %h<c>; | |
} |
Kind of tedious, right?
Perl signature parameter destructuring to the rescue! We can do all that right in the subroutine signature itself ― just put a sub-signature behind.
sub myfunc(%h (Int :$a, Str :$b, Rat :$c)) {...}
Destructuring JSON
Pretty nice, but what if you’ve got something even MORE complicated.
Say a chunk of JSON with nested structures, parts that might be missing that need defaults, etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use JSON::Fast; | |
my $item = from-json(q:to/END/); | |
{ | |
"book" : { | |
"title" : "A Christmas Carol", | |
"author" : "Charles Dickens" | |
}, | |
"count" : 12, | |
"tags" : [ "christmas", "santa"] | |
} | |
END |
The q:to/END/
is a Perl 6 heredoc that just sucks in the text verbatim up to the END, then we can use the JSON::Fast from-json()
to parse it into a perl data structure. You can describe your whole JSON structure right in the signature for a function that you want to receive something like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sub myfunc(% (:%book (Str:D :$title, Str:D :$author), Int :$count, | |
:@tags ($first-tag, *@other-tags)) ) | |
{…} |
Now in the body, I can refer to the parts as just $title
, $author
, $count
, and @tags
. I’ve also split out tags into $first-tag
and @other-tags
for convenience.
Don’t forget you can use Signatures for Blocks too
Sure, Signatures are fantastic for subroutines, but you can also use them, and the destructuring, for a block. Suppose you had an array of the JSON items above, and wanted to iterate through them with a for
loop? Just use the destructuring signature for the pointy block of the for
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for @itemlist -> % (:%book (Str:D :$title, Str:D :$author), Int :$count, | |
:@tags ($first-tag, *@other-tags)) | |
{ | |
say "$title, $author, $count, @tags[], $first-tag, @other-tags[]" | |
} |
Note in this case I don’t even need the hash itself, so I’ve omitted a name for it, using just % by itself for an anonymous Hash (Associative).
Destructuring even works for Objects!
Have you ever tried to walk through an array of objects where the first thing you do is call a few accessors to grab some of the attributes? Sure, you could just use .attribute
with the topicalized iterator, but with a sub-signature, you can do even more.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Book { | |
has $.title; | |
has $.author; | |
has $.count; | |
has @.tags; | |
} | |
my @booklist = | |
Book.new(title => 'A Christmas Carol', | |
author => 'Charles Dickens', | |
count => 12, | |
tags => <ghost christmas>), | |
Book.new(title => 'A Visit from St. Nicholas', | |
author => 'Clement Clarke Moore', | |
count => 4, | |
tags => <santa christmas>); | |
for @booklist -> Book $b (:$title,:$author, :$count, :@tags) { | |
say "$title, $author, $count, @tags[]"; | |
} |
If you want to check for type, or definedness, or set a default, you can do it right in the Signature. If you don’t like the object attribute names, you can use aliases to rename them as you please too.
Conclusion
I’ve found destructuring parameters very useful in interacting with database query results and JSON. You can use any of the other features of signatures like this, including specifying type, definedness, optionalness, default values, renaming with aliases, limiting with subsets or “where” clauses, slurpies, etc.
Happy Holidays!