Day 13: Junctions

Among the many exciting things in Perl 6, junctions are one of my favourites. While I know I don’t really comprehend everything you can do with them, there are a few useful tricks which I think most people will appreciate, and it is those which I’m going to cover as today’s gift.

Junctions are values which have (potentially) more than one value at once. That sounds odd, so let’s get thinking about some code which uses them. First, let’s take an example. Suppose you want to check a variable for a match against a set of numbers:

if $var == 3 || $var == 5 || $var == 7 { ... }

I’ve never liked that kind of testing, seeing as how it requires much repetition. With an any junction we can rewrite this test:

if $var == any(3, 5, 7) { ... }

How does this work? Right near the core of Perl 6 is a concept called junctive autothreading. What this means is that, most of the time, you can pass a junction to anything expecting a single value. The code will run for each member of the junction, and the result will be all those results combined in the same kind of junction which was originally passed.

In the sample above, the infix:<==> operator is run for each element of the junction to compare them with $var. The results of each test are combined into a new any junction, which is then evaluated in Boolean context by the if statement. An any junction in Boolean context is true if any of its values are true, so if $var matches any value in the junction, the test will pass.

This can save a lot of duplicated code, and looks quite elegant. There’s another way to write it, as any junctions can also be constructed using the infix:<|> operator:

if $var == 3|5|7 { ... }

What if you want to invert this kind of test? There’s another kind of junction that’s very helpful, and it’s called none:

if $var == none(3, 5, 7) { ... }

As you may have guessed, a none junction in Boolean context is true only if none of its elements are true.

Junctive autothreading also applies in other circumstances, such as:

my $j = any(1, 2, 3);
my $k = $j + 2;

What will this do? By analogy to the first example, you can probably guess that $k will end up being any(3, 4, 5).

There is an important point to note in these examples. We’re talking about junctive autothreading, which should give you a hint. By the Perl 6 spec, the compiler is free to run these multiple operations on junctions in different threads so that they can execute in parallel. Much as with hyperoperators, you need to be aware that this could happen and avoid anything which would make a mess if run simultaneously.

The last thing I want to talk about is how junctions work with smartmatching. This is really just another instance of autothreading, but there are some other junction types which become particularly useful with smartmatching.

Say you have a text string, and you want to see if it matches all of a set of regexes:

$string ~~ /<first>/ & /<second>/ & /<third>/

Assuming, of course, you have defined regexes called first, second and third. Rather like |, & is an infix operator which creates junctions, this time all junctions which are only true if all their members are true.

The great thing about junctions is that they have this behaviour without the routine you’re passing them to having to know about it, so you can pass junctions to almost any library or core function and expect this kind of behaviour (it is possible for a routine to deliberately notice junctions and treat them how it prefers rather than using the normal autothreading mechanism). So if you have a routine which takes a value to smartmatch something against, you can pass it a junction and get that flexibility in the smartmatch for free. We use this in the Perl 6 test suite, with functions like Test::Util::is_run, which runs some code in another interpreter and smartmatches against its output.

To finish off, here are some other useful things you can do with junctions. First, checking if $value is present in @list:

any(@list) == $value

Junction constructors can work quite happily with the elements of arrays, so this opens up many possibilities. Others include:

all(@list) > 0; # All members greater than zero?
all(@a) == any(@b); # All elements of @a present in @b?

Go experiment, and have fun!