Day 6 – The X and Z metaoperators

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:

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:<~>?

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.

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.

Like this:

LikeLoading...

Related

This entry was posted on December 6, 2010 at 12:00 am and is filed under 2010. You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.

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

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

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.

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:

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.