"With great power comes great responsibility." These days I muse on how this applies not just to movie superheroes, but to programming language features as well.
It happens all over the place. Database connection? Awesome. Well, until you become the victim of an SQL injection or a SELECT N+1 problem. Regular expressions? Also awesome... until someone uses it to parse HTML and gets a visitation from elder non-beings beyond the fabric of reality. Ouch.
Of course, we still like our powerful features. But we need to learn to contain them, to harness them. The awesome should work for us and the purpose we have set it to. It should not work for attackers, elder non-beings, or Murphy. We need to make sure the awesome doesn't leak beyond its designated boundaries.
In 2009, Matthew Walton blogged about junctions. His explanation of what makes junctions exciting is great, and I agree with it. Today I would simply like to add the following point: junctions are so awesome that they should be contained.
Let's demonstrate the problem real quickly.
> my @list = 3..7
3 4 5 6 7
> my $answer = all(@list) > 0;
Quick, what value ends up in $answer
? Well, all the values in @list
are
indeed greater than 0, so...
all(True, True, True, True, True)
Aw, dang. Not again.
Just to be clear, this is by spec, and correctly implemented and everything. But unless you ask specifically for Perl 6 to collapse the junction, it just keeps on going. Keeps on going, like a juggernaut, through walls and oceans, propagating through your program whether you want to or not.
What you have to do then is to collapse the junction, putting a cap on all
that awesome. The junction is useful as long as you're doing the junctive
computation itself (in this case all(@list) > 0
), but immediately after that,
we'll want to collapse back into the normal, sane, non-junctive world.
> ?$answer # symbolic prefix
True
> so $answer # listop prefix
True
> $answer.Bool # coerce method
True
I will hasten to add that the most common use of junctions don't suffer from
this problem, namely junctions in if
statements:
if all(@list) > 0 {
...
}
The if
statement itself provides a natural cap to the junction. It does its
own boolification of it under the hood, but more importantly, the junction
value doesn't stick around. We only see the effects of the boolean value of the
junction.
No, the real risk comes when you're a library writer, and you provide routines that happen to compute things using junctions:
sub all-positive(@list) {
all(@list) > 0; # DANGER
}
That's where you want to remember the old tired adage. With great power... comes... right, that's right. So you cap your junction before you let it run out into the wild and do untold structural damage to people, cattle, and those cute fluffy dogs that would otherwise suffer Puppy Death By Junction.
sub all-positive(@list) {
so all(@list) > 0;
}
Yes. That's better.
If you declare your return type, you can get a runtime error for leaving out the so
.
sub all-positive(@list --> Bool) {
all(@list) > 0;
}
say all-positive([1, 2, 3]); # Type check failed for return value; expected 'Bool' but got 'Junction'
Maybe that's an argument for declaring return types for your routines. And maybe some day we'll catch that one at compile-time, too.
Cap your junctions as early as possible.
— masak's Rule of Responsible Junction Use
The longest I can remember legitimately keeping junctions around in an
uncollapsed state was when storing junctions of regexes in a hash as part of
this password-analyzing script.
Even that's not an exception to the above rule, though — note that the
first thing that happens to the junctions in the subsequent code is that
they become the rhs in a smartmatch statement, and then get capped by an
if
statement. So even here, they don't escape out into the wild.
Code responsibly. Cap your junctions.