Day 6: Going Into Hyperspace

pmichaud introduced Perl 6’s hyper operators yesterday. I’d like to explore these powerful meta operators further.

First, for simplicity I’m going to code a helper function, lsay, to easily get nice-looking output of lists. The sub is created using our so you can use it on the REPL.

our sub lsay(@a) { @a.perl.say }

Then we can start looking at hyperoperator examples. For this post I’m going to use >> and << instead of » and «, mostly because they are easier on my eyes. (I’m afraid I may need to get glasses.) » and « are generally considered the true form of the operator, but the longer ASCII version will work as well.

First, the most basic: adding two lists of the same length:

> lsay (1, 2, 3, 4) <<+>> (3, 1, 3, 1)
[4, 3, 6, 5]
> lsay (1, 2, 3, 4) >>+<< (3, 1, 3, 1)
[4, 3, 6, 5]

If the lengths of the arrays are the same, there’s no difference between the two forms. But if the length is different:

> lsay (1, 2, 3, 4) <<+>> (3, 1)
[4, 3, 4, 5]
> lsay (1, 2, 3, 4) >>+<< (3, 1)
Sorry, right side is too short and not dwimmy.

The rule is that whatever is pointed to by the pointy end of the hyperoperator can be extended if it is shorter than the other end; it is extended by repeating the last element of that list. Whatever is at the blunt end of the hyperoperator cannot be extended. All combinations are allowed, so you can specify that only the left side can be extended (<<+<<), only the right side (>>+>>), both sides can be extended (<<+>>), or neither side can be extended (>>+<<). Single scalars extend as well:

> lsay (1, 2, 3, 4) >>+>> 2
[3, 4, 5, 6]
> lsay 3 <<+<< (1, 2, 3, 4)
[4, 5, 6, 7]

So that’s the basics of using hyperoperator with an infix operator. You can also use them with prefix and postfix operators:

> lsay ~<<(1, 2, 3, 4)
["1", "2", "3", "4"]
> my @a= (1, 2, 3, 4); @a>>++; lsay @a;
[2, 3, 4, 5]

You can also:

> lsay (0, pi/4, pi/2, pi, 2*pi)>>.sin
[0, 0.707106781186547, 1, 1.22464679914735e-16, -2.44929359829471e-16]
> lsay (-1, 0, 3, 42)>>.Str
["-1", "0", "3", "42"]

That is to say >>. works to call a method on every member of the list.

However much you are tempted to write @array>>.say, don’t do it. It may work in the current version of Rakudo, but by using the hyper operator you are promising the operation is parallelizable, and the order of the operations on the list(s) is not fixed. The hope is that future versions of Perl 6 will automatically run these operations in parallel.

Other quick notes: The hyperoperators don’t just work with the built-in set of operators. They will work with any new operator you define as well. (That works now in Rakudo, mostly.) They will work with the in-place operators, e.g. @a >>/=>> 2 to divide an entire array by 2. (This does not work in current Rakudo.) They will work with multi-dimensional lists, with trees, and with hashes; see S03 Hyper operators. (As far as I know, these do not yet work in Rakudo either.)

I don’t know too many examples yet of source code using hyperoperators extensively, though LastOfTheCarelessMen’s Vector class is a good if straightforward start — it implements an N-dimensional vector class without a single explicit loop.

Day 5: Metaoperators

In the Day 4 box, we saw an interesting implementation for the factorial function:

sub fac(Int \$n) {
[*] 1..\$n
}

Okay, so how does that work? Opening up today’s Advent box provides some answers!

Perl 6 has a number of different “meta operators” that modify the existing operators to perform more powerful functions.

The square brackets about are an example of the “reduce metaoperator”; they cause an infix operator to become a list operator that acts as though the infix was placed between each pair of elements. For example, the expression

[+]  1, \$a, 5, \$b

is essentially the same as writing

1 + \$a + 5 + \$b

This gives us a handy mechanism to “sum all elements in a list”:

\$sum = [+] @a;            # sum all elements of @a

Most of the infix operators (including user-defined operators) can be placed inside of square brackets to turn them into reductions:

\$prod = [*] @a;           # multiply all elements of @a

\$mean = ([+] @a) / @a;    # calculate mean of @a

\$sorted = [<=] @a;        # true if elements of @a are numerically sorted

\$min = [min] @a, @b;      # find the smallest element of @a and @b combined

So, in the factorial subroutine above, the expression [*] 1..\$n returns the product of multiplying all of 1 through \$n together.

Another useful metaoperator is the “hyper” operator. Placing »
and/or « (or the ASCII >> and << equivalents) next to an operator makes it “hyper”, which causes it operate on elements of lists. For example, the following calculates @c as the pairwise addition of the elements in @a and @b:

@c = @a »+« @b;

In Perl 5, we’d generally write something like

for (\$i = 0; \$i < @a; \$i++) {
\$c[\$i] = \$a[\$i] + \$b[\$i];
}

which is quite a bit longer.

As with the square brackets above, we can use hyper on a variety of operators, including user-defined operators:

# increment all elements of @xyz
@xyz»++

# each element of @x is the smaller of @a and @b
@x = @a »min« @b;

We can also flip the angles to enable a scalar to act like an array:

# multiply each element of @a by 3.5
@b = @a »*» 3.5;

# multiply each element of @x by \$m and add \$b
@y = @x »*» \$m »+» \$b;

# invert all elements of @x
@inv = 1 «/« @x;

# concatenate @last, @first to produce @full
@full = (@last »~» ', ') »~« @first;

Of course, reductions and hyper operators can be combined in expressions:

# calculate the sum of squares of @x
\$sumsq = [+] ( @x »**» 2);

There are many other metaoperators available, including X (cross), R (reverse), S (sequential). In fact, the “in-place” operators such as +=, *=, ~=, are just meta forms created by suffixing an operator with an equals sign:

\$a += 5;      # same as \$a = \$a + 5;
\$b //= 7;     # same as \$b = \$b // 7;
\$c min= \$d;   # same as \$c = \$c min \$d;