Day 5 – Destructure your Arguments with Perl 6 Signatures

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:


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;
}

view raw

myfunc1.p6

hosted with ❤ by GitHub

If I want to also simplify the way I refer to those fields, I can assign them to other variables:


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>;
}

view raw

myfunc2.p6

hosted with ❤ by GitHub

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.


use JSON::Fast;
my $item = from-json(q:to/END/);
{
"book" : {
"title" : "A Christmas Carol",
"author" : "Charles Dickens"
},
"count" : 12,
"tags" : [ "christmas", "santa"]
}
END

view raw

jsonchunk.p6

hosted with ❤ by GitHub

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:


sub myfunc(% (:%book (Str:D :$title, Str:D :$author), Int :$count,
:@tags ($first-tag, *@other-tags)) )
{}

view raw

myfunc3.p6

hosted with ❤ by GitHub

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:


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[]"
}

view raw

forexample.p6

hosted with ❤ by GitHub

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.


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!