Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

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

Raku: A very simple list

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

Raku: Using Hash

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

Raku: The Songs Module Containing Array of Records

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;

Raku: Using Songs Module

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.

Raku: Using Songs Module

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.

Raku: Using Songs Module

❯ ./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> ));
  }
}

Raku: Using Songs Module

Or this one using unicode element operator below:

for @songs {
  if ('tags'  %$_.keys) {
    say %$_<title> ~ " is "
      ~ join(":", @( %$_<tags> ));
  }
}

Raku: Using Songs Module


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

Raku: Extracting Hash Records

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;

Raku: Flattening List of List

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.

Raku: The Flatten Function

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

Raku: Flattening List of List

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

Raku: Solving Unique Song


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 ].