Day 09 – Hashes and pairs

Hashes are nice. They can work as a kind of “poor man’s objects” when creating a class seems like just too much ceremony for the occasion.

my $employee = {
    name => 'Fred',
    age => 51,
    skills => <sweeping accounting barking>,
};

Perl (both 5 and 6) takes hashes seriously. So seriously, in fact, that there’s a dedicated sigil for them, and by using it you can omit the braces in the above code:

my %employee =
    name => 'Fred',
    age => 51,
    skills => <sweeping accounting barking>,
;

Note that Perl 6, just as Perl 5, allows you to use barewords for hash keys. People coming from Perl 5 seem to expect that we’ve dropped this feature — I don’t know why, but I suspect that as much as they like the ability, they also feel that it’s secretly “dirty” or “wrong” somehow, and thus they just assume that hash keys need to be quotes in Perl 6. Don’t worry, fivers, you can omit the quotes there without any feelings of guilt!

Another nice thing is that final comma. Yes, Perl allows a comma even after the last hash entry. This makes rearranging lines later a lot less of a hair-pulling experience, because all lines have a final comma. So a big win for maintainability, and not a lot of extra bookkeeping for the Perl 6 parser.

Hashes make great “configuration objects”, too. You want to pass some options into a routine somewhere, but the options (for reasons of future compatibility, perhaps) need to be an open set.

my %options =
    rpm => 440,
    duration => 60,
;
$centrifuge.start(%options);

Actually, we have two options with that last line. Either we pass in the whole hash like that, and the method in the centrifuge class will need to look like this:

method start(%options) {
    # probably need to start by unpacking options here
    # ...
}

Or we decide to “gut” the hash as we pass it in, effectively turning it into a bunch of named arguments:

$centrifuge.start( |%options );  # means :rpm(440), :duration(60)

In which case the method declaration will have to look like this instead:

method start(:$rpm!, :$duration!) {
    # ...
}

(In this case, we probably want to put in those exclamation marks, to make those named parameters obligatory. Unless we’re fine with providing some of them with a default, such as :$duration = 120.)

The “gut” operator prefix:<|> is really called “flattening” or “interpolation”. I really like how, in Perl 6, arrays flatten into positional parameters, and hashes flatten into named parameters. Decades after the fact, it gives some extra rationale to making arrays and hashes special enough to have their own sigils.

my @args = "Would you like fries with that?", 15, 5;
say substr(|@args);    # fries

my %details = :year(1969), :month(7), :day(16),
              :hour(20), :minute(17);
my $moonlanding = DateTime.new( |%details );

This brings us neatly into my next point: hash entries in Perl 6 really try to look like named parameters. They aren’t, of course, they’re just keys and values in a hash. But they try really hard to look the same. So much so that we even have *two* syntaxes for writing a hash entry. We have the fat-arrow syntax:

my %opts = blackberries => 42;

And we have the named argument syntax:

my %opts = :blackberries(42);

They each have their pros and cons. One of the nice things about the latter syntax is that it mixes nicely with variables, and (in case your variables are fortunately named) eliminates a bit of redundancy:

my $blackberries = 42;
my %opts = :$blackberries;   # means the same as :blackberries($blackberries)

We can’t do that in the fat-arrow syntax, not without repeating the word blackberries. And no-one likes to do that.

So hash entries (a key plus a value) really become more of a thing in Perl 6 than they ever were in Perl 5. In Perl 5 they’re a bit of a visual hack, since the fat-arrow is just a synonym for the comma, and hashes are initialized through lists.

In Perl 6, hash entries are syntactically pulled into visual pills through the :blackberries(42) syntax, and even more so through the :$blackberries syntax. Not only that, but we’re passing hashes into routines entry by entry, making the entries stand out a bit more.

In the end, we give up and realize that we care a bunch about those hash entries as units, so we give them a name: Pair. A hash is an (unordered) bunch of Pair objects.

So when you’re saying this:

say %employee.elems;

And the answer comes back “3”… that’s the number of Pair objects in the hash that were counted.

But in the end, Pair objects even turn out to have a sort of independent existence, graduating from their role as hash constituents. For, example, you can treat them as cons pairs and simulate Lisp lists with them:

my $lisp-list = 1 => 2 => 3 => Nil;  # it's nice that infix:<< => >> is right-associative

And then, as a final trick, let’s dynamically extend the Pair class to recognize arbitrary cadr-like method calls. (Note that .^add_fallback is not in the spec and currently Rakudo-only.)

Pair.^add_fallback(
    -> $, $name { $name ~~ /^c<[ad]>+r$/ },  # should we handle this? yes, if /^c<[ad]>+r$/
    -> $, $name {                            # if it turned out to be our job, this is what we do
        -> $p {
            $name ~~ /^c(<[ad]>*)(<[ad]>)r$/;        # split out last 'a' or 'd'
            my $r = $1 eq 'a' ?? $p.key !! $p.value; # choose key or value
            $0 ne '' ?? $r."c{$0}r"() !! $r;               # maybe recurse
        } 
    }
);

$lisp-list.caddr.say;    # 3

Whee!

8 thoughts on “Day 09 – Hashes and pairs

    1. Somehow the list it was supposed to hold got lost on the way. I fixed it for the original author, based off of an earlier draft.

      Thanks for spotting it!

  1. I also meant to give credit for the .^add_fallback implementation, which was provided on #perl6 by benabik++ in response to a general challenge to write a fallback that handled general cdaddr-like method calls.

    All I did was edit for whitespace and change things around slightly.

  2. I have been following Perl6 since 2001, and I really admire you guys developing it. There are so many niceities in there. But when I see examples like the last one; Is this APL++? Very few will show any interest in a language with this syntax. No matter how fantastic it is.

    1. The code at the end is in a compact style, implicitly employs the meta-class, and involves regexes, pointy blocks, anonymous variables, and the new ternary operator. Beyond that, it assumes you know what the arguments of the implementation-dependent method .^add_fallback does. Yes, of course the syntax will be unusual to most people.

      But you’re missing the point: we just added an infinity of methods to Pair, in ten lines of code. And they’re not even ten lines of golfed code, they just assume an active knowledge of various bits of Perl 6 syntax.

      Yes, it will look incomprehensible if you don’t know those bits. But learn them, and you will find that most of these are very natural extensions of Perl 5: the regex syntax is similar but tidier, pointy blocks are an extension of Perl 5 subroutines, anonymous variables are simply variables that you don’t care to name, and the new ternary operator is like the old one, but spelled differently.

      Conjecture: any sufficiently advanced cool trick will be indistinguishable from APL++ to those who haven’t studied the syntax.

    2. Lars,

      If someone doesn’t know beginner P6, the cadr code will look simultaneously totally impenetrable and absolutely ridiculous. It’s far beyond line noise.

      Even if someone *does* know a little P6, it’ll still look non-sensical if one does *not* know what “cadr-like method calls” are. You really have to know that aspect of lisp or it’ll still look absurd.

      If you know a little P6, and know what “cadr-like method calls” are, it’s elegant, almost entirely obvious code, and impressively powerful.

  3. Hi Carl and Raiph, Thanks for the replies.
    Yes I probably missed the point I often do, but it looks like you understood mine.
    Carl I understand you live in Uppsala, if you happen to be in Stockholm and have an hour or so with nothing to do, you are welcome for a chat about Perl6, I can offer you a lunch.

    Lars Johansson
    Lead Enterprise Information Architect, Corporate IT
    Atlas Copco AB, 105 23 Stockholm, Sweden
    Phone: +46(0)8 7439563

    1. Thanks for the nice offer. I no longer live in Uppsala, but I’m teaching in Stockholm quite a lot, so it should be possible to meet up sometime. I’ll keep in touch.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.