Day 6 – The X and Z metaoperators

by

One of the new ideas in Perl 6 is the metaoperator, an operator which combines with an ordinary operator in order to change its behaviour. There are several of them, but in this post we will concentrate on just two of them: X and Z.

The X operator is one you might already have seen in its ordinary role as the infix cross operator. It combines lists together, one element from each, in every combination:

> say ((1, 2) X ('a', 'b')).perl
((1, "a"), (1, "b"), (2, "a"), (2, "b"))

However, this infix:<X> operator is actually just a shorthand for the X metaoperator applied to the list concatenation operator infix:<,>. Indeed, you’re perfectly at liberty to write

> say ((1, 2) X, (10, 11)).perl
((1, 10), (1, 11), (2, 10), (2, 11))

If you like. So what happens if you apply X to a different infix operator? How about infix:<+>

> say ((1, 2) X+ (10, 11)).perl
(11, 12, 12, 13)

What’s it done? Instead of creating a list of all the elements picked for each combination, the operator applies the addition infix to them, and the result is not a list but a single number, the sum of all the elements in that combination.

This works for any infix operator you care to use. How about string concatenation, infix:<~>?

> say ((1, 2) X~ (10, 11)).perl
("110", "111", "210", "211")

Or perhaps the numeric equality operator infix:<==>?

> say ((1, 2) X== (1, 1)).perl
(Bool::True, Bool::True, Bool::False, Bool::False)

But this post is also meant to be about the Z metaoperator. We expect you may have already figured out what it does, if you’ve encountered the infix:<Z> operator before, which is of course just a shortcut for Z,. If a Haskell programmer understands infix:<Z> as being like the zip function, then the Z metaoperator is like zipWith.

> say ((1, 2) Z, (3, 4)).perl
((1, 3), (2, 4))
> say ((1, 2) Z+ (3, 4)).perl
(4, 6)
> say ((1, 2) Z== (1, 1)).perl
(Bool::True, Bool::False)

Z, then, operates on each element of each list in turn, working on the first elements together, then the second, then the third for however many there are. It stops when it reaches the end of a list regardless of which side that list is on.

Z is also lazy, so you can apply it to two infinite lists and it will only generate as many results as you need. X can only handle an infinite list on the left, otherwise it would never manage to get anywhere at all.

At the time of writing, Rakudo appears to suffer from a bug where infix:<Z> and infix:<Z,> are not identical: the former produces a flattened result list. S03 shows that the behaviour of the latter is correct.

These metaoperators, then, become powerful tools for performing operations encompassing the individual elements of multiple lists, whether those elements are associated in some way based on their indexes as with Z, or whether you just want to examine all possible combinations with X.

Got a list of keys and values and you want to make a hash? Easy!

my %hash = @keys Z=> @values;

Or perhaps you want to iterate over two lists in parallel?

for @a Z @b -> $a, $b { ... }

Or three?

for @a Z @b Z @c -> $a, $b, $c { ... }

Or maybe you want to find out all the possible totals you could get from rolling three ten-sided dice:

my @d10 = 1 ... 10;
my @scores = (@d10 X+ @d10) X+ @d10;

If you want to see some real-world use of these metaoperators, Sudoku.pm in Moritz Lenz’s Sudoku solver.

About these ads

Tags:

10 Responses to “Day 6 – The X and Z metaoperators”

  1. Christoph Says:

    > X can only handle an infinite list on the left, otherwise it would never manage to get anywhere at all.

    This restriction only applies because S03 demands ordered results (“The returned lists are ordered such that the rightmost elements vary most rapidly.”) If you drop this requirement, X could easily take infinite lists on both sides (cross-products of countable sets are still countable)

  2. Dean Says:

    Is the Zip operator always equivalent to the corresponding hyper-operator?

    > (1,2) Z (3,4)
    1 3 2 4
    > (1,2) >>,< (1,2) Z+ (3,4)
    4 6
    > (1,2) >>+<< (3,4)
    4 6

    • colomon Says:

      No, it’s only “equivalent” in the simple cases. The big difference is that zip is lazy and sequential, while hyper is eager and may execute in any order.

    • Larry Wall Says:

      Some additional differences are that hypers can work dwimmily on hierarchical values, while X and Z cannot, because they’re more list oriented. Also, since X and Z are looser than comma, you don’t have to parenthesize comma lists as you must with (most) hyper infixes. That is, hypers are transparent to the precedence of their base operator, while X and Z (and metasequences based on them) are forced to list infix precedence.

  3. pete_mcstubbins Says:

    Can anyone explain why the “.perl” is needed at the end of an expression for the code which begins with “say”? Thank you in advance.

    • Matthew Walton Says:

      I certainly can. It’s just about output formatting for the sake of these examples. The .perl method in Perl 6 returns a string representation of the object which could be fed back through the compiler to reproduce the same object (in theory – it doesn’t work for absolutely everything. It’s a bit like Data::Dumper, if you know that from Perl 5). So I used .perl when I was writing the post in order to get the output in a form which shows the structure of the produced lists. If I’d omitted it, I’d have got the standard results from converting a list to a string, which would be something more like:

      > say (1, 2 X 3, 4)
      13142324

      Far less useful for the illustrative examples in the post.

  4. prakash Says:

    $ perl6 -v

    This is Rakudo Perl 6, version 2010.11-15-gfedc117 built on parrot 2.10.1 RELEASE_2_10_1-679-g9bec614

    $ perl6
    > ((1, 2) Z, (3, 4)).perl
    ((1, 3), (2, 4))
    > (1, 2) Z, (3, 4)
    2 4 2 4

    Why are the outputs different?

    Thanks.

    • colomon Says:

      Because (1, 2) Z, (3, 4) is the equivalent of ((1, 2) Z, (3, 4)).Str, which is doing something along the lines of “Take each element in the list, convert it to a string, and return them separated by spaces.” .perl is more akin to Data::Dumper.

    • timtoady Says:

      As for why it says 2 4 2 4 rather than 1 3 2 4, that’s just a rakudobug that appears to involve accidental aliasing of returned variables.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

Join 43 other followers

%d bloggers like this: