Author Archive

Paired up Hashes

December 20, 2011

What is possible with arrays and lists in Perl 6 is truly remarkable and was demonstrated here several times. But what about hashes?
Superficially not much has changed.
(Following Damian’s rule from PBP to name a hash variable in singular.)

    %song = Panacea => 'found a lover', Photek => 'ni ten ichi ryu';
    say keys %song;   # also %song.keys
    say values %song; # %song.values

Yes, the sigils are now invariant, so you get values with:

    %song{'Panacea'}
    %song{'Panacea', 'Photek'}

That can be shortened, because <> is the new qw():

    %song<Panacea>
    %song<Panacea Photek>

Frankly, almost everything else has changed. Perl 6 can be sometimes hideous, just mimicking to be your good old pal Perl 5 while being a friendly T-X, blasting behind your back your programming problems away. The fat arrow is no longer a fancy comma but an infix operator, creating an object that contains a key-value pair.

    my $song = paniq => 'Godshatter';
    say $song.WHAT; # says: "Pair()"
    $song.key;     # as expected is paniq
    $song.value;  # you guess it

There is another Syntax for that, heavily used in signatures:

    my $song = :paniq('Godshatter');

But what happens if I:

    my @songs = %song; # same as @(%songs)

You maybe predicted it, @songs gets a list of pairs. For the old behaviour, you have to say explicitly: “I want the keys and values as a list.”:

    my @songs = %song.kv; # key 1, value 1, key 2 ....

This new setup of hashes is not only theoretically very pleasing. It also allows iterating over hashes, without the risk of loosing the precious key => value correlation. That’s handy for all kinds of sorting and mashing of data, for which Perl is famous. What else did Randal L. Schwartz once upon a time than creating a list of pairs, sorting them and then picking the needed data bits.

Having pairs as a built-in type helps also subs and methods to handle their parameters. Some of the can be positional, which could be ordered in an array. Some of them are named and could be stored in a hash. But they are actually stored in a “Parcel”, a list that can contain pairs. This way the order of the parameters and the key => value correlations are preserved.

A very similar type is the Capture, which can hold all arguments sent to a routine. Therefore it has to behave more like routine and pass all the named arguments under their names. But if you ask for the positional parameter, you get only them, not the named ones. With a Capture full of values you can ask with a smartmatch if it would pass a certain subroutine and many fine things more. The vaults are going here deeper and deeper, but lets get back to the daylight of everyday hash-usage.

Panacea aka Mathis Mootz had a lot of great tracks. And when I do:

    %song<Panacea> = "state of extacy";

“found a lover” gets overwritten. Nothing new so far, but there are times I just don’t want to loose my data. Then I need to execute some force onto the hash.

    %song.push( Panacea => "state of extacy" );

Whole lists of pairs can be pushed into another hash this way. The result will be (not surprisingly) still a hash but the key ‘Panacea’ now points to an array, containing both song titles. That’s also useful when inverting a hash, that means pulling out a pair-list where key and value are flipped. Pasting that into a hash may lead to collisions, if several keys have the same value. A simple:

    # list with song => artist pairs
    %song = %artist.invert;

might produce losses, but the following does not:

    %song.push( %artist.invert ); # or:
    %song.push: %artist.invert;

While doing some heavy data munging you might regroup the values under a different set of keys. In that case it is likely that several values will end up under the same key. Given you have a sub that recognizes a genre of a song, you might write something like:

    map { %genre.push(genre_of_song($_) => $_) }, %song.values;

But as you already guessed it, there is an easier way to do that:

    %genre = classify { genre_of_song($_) }, %song.values;

Now you probably say: “That’s unrealistic!”. There are songs for instance from “Magnetic Man” that can be labeled “Drum ‘n Base”, “Dubstep” or even “Pop”. Larry knows that. (This kind of problem of course.) That’s why he pulled out a second hash generating method.

    %genre = categorize { genre_of_song($_) }, %song.values;

Unlike classify, which expects exactly one value (Called scalar in P5 but item in Perl 6 world), categorize can handle a list of results returned by the closure (block). It also happily accepts a Nil, which means, unlike undef in Perl 5, really nothing. When classify gets a Nil, the song will then not appear in any category, meaning under no hash key.

Imagine a routine of real artificial intelligence that can distinct good from bad songs. (Your definition of good of course!)

    my %quality = classify { good_music($_) ?? 'good' !! 'bad' }, %song.values;

Hence %quality<good> contains all the songs I sent to my music player and %quality<bad> to /dev/null (which is the name of another electronic music artist).


Follow

Get every new post delivered to your Inbox.

Join 36 other followers