Preface
Goal: A practical case to collect unique record fields using Raku.
Raku (formerly Perl 6) was evolved version of Perl 5. It has very similar with its Perl5 ancestor, but with modern approach.
Reference Reading
The last time I read raku documentation thoroughly, was two decades ago.
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
unit module MySongs;
our @songs is export =
{ title => 'Cantaloupe Island',
tags => ('60s', 'jazz') },
{ title => 'Let It Be',
tags => ('60s', 'rock') },
{ title => 'Knockin\' on Heaven\'s Door',
tags => ('70s', 'rock') },
{ title => 'Emotion',
tags => ('70s', 'pop') },
{ title => 'The River'}
;
Raku Solution
The Answer
I use list comprehension like syntax using map
and grep
,
such as this oneliner as below:
#!/usr/bin/raku
use lib $*PROGRAM.dirname;
use MySongs;
my @songs_tags = ((@songs
.grep: 'tags' ∈ *.keys)
.map: *<tags>);
my @tags = @songs_tags.List.flat.unique;
join(":", @tags).say;
- Enough with introduction, at this point we should go straight to coding.
Environment
In Void linux, you can just install Raku. Just run and voila..!
But with AUR, I have to intsall these three:
- moarvm-git
- nqp-git
- rakudo-git
$ yay -S moarvm-git nqp-git rakudo-git
1: Data Structure Using Dictionary
We can use list
or array
.
But list
in data structure is easier to handle.
So we are going to use list
,
throught out this article.
Simple List
Consider begin with simple list
.
#!/usr/bin/raku
my @tags = ('rock',
'jazz', 'rock', 'pop', 'pop');
print join(':', @tags) ~ "\n";
Unlike Perl, you can write signature.
my Str @tags = ('rock',
'jazz', 'rock', 'pop', 'pop');
Just like Perl,
it is easy to dump array
in raku
using join
.
With the result similar as below list
:
❯ raku 01-tags.raku
rock:jazz:rock:pop:pop
Shebang
With shebang
such as #!/usr/bin/raku
,
you do not need to type raku
in CLI.
Instead of
❯ raku 02-song.raku
You can just type
❯ ./02-song.raku
And you can run the script directly in your text editor.
Such as using geany, you just can hit the F5
key to run the script.
Hash
We can use hash
to store our record.
my %song =
title => 'Cantaloupe Island',
tags => ('60s', 'jazz')
;
Or with signature:
my %song{Str} =
title => 'Cantaloupe Island',
tags => ('60s', 'jazz')
;
And examine how to access the array inside the hash
.
my @tags = %song<tags>;
say %song<title>;
say join(":", @tags.List);
say %song<tags>[1];
You should be careful with these symbol:
$
for scalar,@
for array, and%
for hash.
Now, examine the result:
❯ ./02-song.raku
Cantaloupe Island
60s:jazz
jazz
The Songs Structure
We can continue our journey to records using list
of hash
.
No need any complex structure.
#!/usr/bin/raku
my @songs =
{ title => 'Cantaloupe Island',
tags => ('60s', 'jazz') },
{ title => 'Let It Be',
tags => ('60s', 'rock') },
{ title => 'Knockin\' on Heaven\'s Door',
tags => ('70s', 'rock') },
{ title => 'Emotion',
tags => ('70s', 'pop') },
{ title => 'The River'}
;
Then process in a loop to produce desired output.
for @songs -> %song_hash {
if (%song_hash<tags>:exists) {
my $tags = %song_hash<tags>;
say %song_hash<title> ~ " is "
~ join(":", @$tags);
}
}
With the result similar as below record:
❯ ./03-songs.raku
Cantaloupe Island is 60s:jazz
Let It Be is 60s:rock
Knockin' on Heaven's Door is 70s:rock
Emotion is 70s:pop
Filtering Approach
I’m using exists
to test if the hash certain key.
%song_hash<tags>:exists
There will be other alternatives as well.
2: Separating Module
Since we need to reuse the songs record multiple times, it is a good idea to separate the record structure from logic.
Songs Module
The code can be shown as below:
Notice the extension is .rakumod
instead of .raku
.
unit module MySongs;
our @songs is export =
{ title => 'Cantaloupe Island',
tags => ('60s', 'jazz') },
{ title => 'Let It Be',
tags => ('60s', 'rock') },
{ title => 'Knockin\' on Heaven\'s Door',
tags => ('70s', 'rock') },
{ title => 'Emotion',
tags => ('70s', 'pop') },
{ title => 'The River'}
;
Module in Relative Path
In order to use module in relative path, we need to add a few more header decalration.
#!/usr/bin/raku
use lib $*PROGRAM.dirname;
use MySongs;
Using Songs Module
Now we can have a shorter code as shown below.
for @MySongs::songs -> %song_hash {
if (%song_hash<tags>:exists) {
my $tags = %song_hash<tags>;
say %song_hash<title> ~ " is "
~ join(":", @$tags);
}
}
With the result exactly the same as previous code.
Using Default Variable
We can even rewrite code above to be a very short code, with advantage of raku syntax.
for @songs {
if (%$_<tags>:exists) {
say %$_<title> ~ " is "
~ join(":", @( %$_<tags> ));
}
}
The result is, exactly the same as previous code.
❯ ./04-module-b.raku
Cantaloupe Island is 60s:jazz
Let It Be is 60s:rock
Knockin' on Heaven's Door is 70s:rock
Emotion is 70s:pop
Alternative Filter Using Keys
There are other alternatives as well such as below code:
for @songs {
if (%$_.keys.grep(/tags/)) {
say %$_<title> ~ " is "
~ join(":", @( %$_<tags> ));
}
}
Or this one using unicode element operator below:
for @songs {
if ('tags' ∈ %$_.keys) {
say %$_<title> ~ " is "
~ join(":", @( %$_<tags> ));
}
}
3: Finishing The Task
Extract, Flatten, Unique
We can solve, this task,
by using imperative approach,
such as for
loop.
And later we can build,
a list comprehension like appproach,
utilizing map
and grep
.
Extracting Hash
Filtering hash, and push to array
Consider start with this long code:
my @tagss = ();
for @MySongs::songs -> %song_hash {
if (%song_hash<tags>:exists) {
my $tags = %song_hash<tags>;
push @tagss, $tags;
}
}
for (@tagss) -> @tags {
join(":", @tags).say;
}
With the result of list
of list
, as shown below.
❯ ./05-extract-a.raku
60s:jazz
60s:rock
70s:rock
70s:pop
We can also have compacted version of above code.
my @tagss = ();
for @songs {
if (%$_<tags>:exists) {
push @tagss, %$_<tags>;
}
}
for (@tagss) {
join(":", @$_).say;
}
With exactly the same result.
Flatten
To flatten the code above, we can directly push array.
To make this clear for you, I give the long version, as shown this code below:
my @tagss = ();
for @MySongs::songs -> %song_hash {
if (%song_hash<tags>:exists) {
my $tags = %song_hash<tags>;
push @tagss, $tags;
}
}
join(":", @tagss.List.flat).say;
With the result of list
of tags, as shown below.
❯ ./06-flatten-a.raku
60s:jazz:60s:rock:70s:rock:70s:pop
Flatten Function
Since we are going to reuse the flatten approach above in other script. It is better to bundle the script in its own raku module.
unit module MyHelperFlatten;
sub flatten is export {
my @tagss = ();
for (@_) {
if (%$_<tags>:exists) {
@tagss.push: @( %$_<tags> ).Slip;
}
}
return @tagss;
}
The return values is an array.
Using Flatten Module
There is nothing to say in this code below.
Just apply flatten
function to our song records.
#!/usr/bin/raku
use lib $*PROGRAM.dirname;
use MySongs;
use MyHelperFlatten;
say join ":", (flatten @songs);
With the result of a flattened array
shown below.
❯ ./06-flatten-b.raku
60s:jazz:60s:rock:70s:rock:70s:pop
Exactly the same as previous code.
Unique
To solve unique
array,
raku
is already has a built in unique
function.
#!/usr/bin/raku
use lib $*PROGRAM.dirname;
use MySongs;
use MyHelperFlatten;
join(":", (flatten @songs).unique).say;
With the result similar as below array:
❯ ./07-unique.raku
60s:jazz:rock:70s:pop
What is Next 🤔?
We have alternative way to extract the record structure. With pattern similar to list comprehension.
Consider continue reading [ Raku - Playing with Records - Part Two ].