The Awesome Errors of Perl 6

If you're following tech stuff, you probably know by now about the folks
at Rust land working on some totally awesome error reporting
capabilities. Since
Perl 6 is also known for its Awesome Errors,
mst inquired for some examples to show
off to the rustaceans, and unfortunately I drew a blank...

Errors are something I try to avoid and rarely read in full. So I figured I'll
hunt down some cool examples of Awesome Errors and write about them. While
I could just bash my head on the keyboard and paste the output, that'd be quite
boring to read, so I'll talk about some of the tricky errors that might not
be obvious to beginners, and how to fix them.

The first line is missing the closing quote on the string, so everything
until the opening quote on the second line is still considered part of the
string. Once the supposedly closing quote is found, Perl 6 sees word "Local,"
which it identifies as a term. Since two terms in a row are not allowed in
Perl 6, the compiler throws an error, offering some suggestions on what it was
expecting, and it detects we're in a string and suggests we check we didn't
forget a closing quote on line 1.

The ===SORRY!=== part doesn't mean you're running the Canadian version
of the compiler, but rather that the error is a compile-time (as compared
to a run-time) error.

Nom-nom-nom-nom

Here's an amusing error. We have a subroutine that returns things, so we call
it and use a for loop to iterate over the values:

Perl 6 lets you omit parentheses when calling subroutines. The error talks
about gobbling blocks. What happens is the block we were hoping to
give to the for loop is actually being passed to the subroutine as an
argument instead. The second error in the output corroborates by saying
the for loop is missing its block (and makes a suggestion it was taken by
the our things subroutine).

The first error tells us how to fix the issue: Function 'things' needs parens,
so our loop needs to be:

for things() {
say "Current stuff is $_";
}

However, were our subroutine actually expecting a block to be passed, no
parentheses would be necessary. Two code blocks side by side would result in
"two terms in a row" error we've seen above, so Perl 6 knows to pass the first
block to the subroutine and use the second block as the body of the for loop:

When Perl 6 encounters names it doesn't recognize it computes
Levenshtein distance for
the things it does know to try to offer a useful suggestion. In the instance
above it encountered an invocation of a subroutine it didn't know about. It
noticed we do have a similar subroutine, so it offered it as an alternative.
No more staring at the screen, trying to figure out where you made the typo!

The feature doesn't consider everything under the moon each time it's
triggered, however. Were we to capitalize the sub's name to Levenshtein, we
would no longer get the suggestion, because for things that start with a
capital letter, the compiler figures it's likely a type name and not a
subroutine name, so it checks for those instead:

Ouch! A run-time error. What's happening is the Seq
type
we get from the the sequence
operator
doesn't keep stuff around. When you iterate over it, each time it gives you
a value, it discards it, so once you've iterated over the entire Seq, you're
done.

Above, we're attempting to iterate over it again, and so the Rakudo runtime
cries and complains, because it can't do it. The error message does offer two
possible solutions.
We can either use the .cache method
to obtain a List we'll iterate over:

And even though we're storing the Seq in an Array, it won't get reified
until it's actually needed:

my @a = 1 … ∞;
say @a[^10];
# OUTPUT:
# (1 2 3 4 5 6 7 8 9 10)

These Aren't The Attributes You're Looking For

Imagine you have a class. In it, you have some private attributes and you've
got a method that does a regex match using the value of that attribute as part
of it:

class {
has $!prefix = 'foo';
method has-prefix ($text) {
so $text ~~ /^ $!prefix/;
}
}.new.has-prefix('foobar').say;
# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Attribute $!prefix not available inside of a regex, since regexes are methods on Cursor.
# Consider storing the attribute in a lexical, and using that in the regex.
# at /home/zoffix/test.p6:4
# ------> so $text ~~ /^ $!prefix⏏/;
# expecting any of:
# infix stopper

Oops! What happened?

It's useful to understand that as far as the parser is concerned, Perl 6 is
actually braided from several languages: Perl 6, Quote, and Regex languages
are parts of that braiding. This is why stuff like this Just Works™:

say "foo { "bar" ~ "meow" } ber ";
# OUTPUT:
# foo barmeow ber

Despite the interpolated code block using the same " quotes to delimit the
strings within it as the quotes on our original string, there's no conflict.
However, the same mechanism presents us with a limitation in regexes, because
in them, the looked up attributes belong to the Cursor object responsible
for the regex.

To avoid the error, simply use a temporary variable to store the $!prefix
in—as suggested by the error message—or use the given block:

De-Ranged

In Perl 6, to index an item from the end of a list, you use funky syntax:
[*-42]. That's actually a closure that takes an argument (which is the
number of elements in the list), subtracts 42 from it, and the return value
is used as an actual index. You could use @a[sub ($total) { $total - 42 }]
instead, if you were particularly bored.

In the error above, that index ends up being 3 - 42, or -39, which is
the value we see in the error message. Since indexes cannot be negative,
we receive the error, which also tells us the index must be 0 to positive
infinity (with any indexes above what the list contains returning Any when
looked up).

A Rose By Any Other Name, Would Code As Sweet

If you're an active user of Perl 6's sister language, the Perl 5, you may
sometimes write Perl-5-isms in your Perl 6 code:

Above, we're attempting to use Perl 5's concatenation operator to concatenate
two strings. The error mechanism is smart enough to detect such usage
and to recommend the use of the correct ~ operator instead.

This is not the only case of such detection. There are many. Here's another
example, detecting accidental use of Perl 5's diamond operator, with
several suggestions of what the programmer may have meant:

while <> {}
# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Unsupported use of <>; in Perl 6 please use lines() to read input, ('') to
# represent a null string or () to represent an empty list
# at /home/zoffix/test.p6:1
# ------> while <⏏> {}

Heredoc, Theredoc, Everywheredoc

Here's an evil error and there's nothing awesome about it, but I figured I'd
mention it, since it's easy to debug if you know about it, and quite annoying
if you don't. The error is evil enough that it may have been already improved
if you're reading this far enough in the future from when I'm writing this.

Try to spot what the problem is... read the error at the bottom first, as if
you were the one who wrote (and so, are familiar with) the code:

Huh? It's crying about an undeclared variable, but it's pointing to a
signature of a subroutine. Of course it won't be declared. What sort of
e-Crack is the compiler smoking?

For those who didn't spot the problem: it's the spurious semicolon after
the closing END of the heredoc. The heredoc ends where the closing delimiter
appears on a line all by itself. As far as the compiler is concerned, we've
not seen the delimiter
at END;, so it continues parsing as if it were still parsing the heredoc.
A qq heredoc lets you interpolate variables, so when the parser gets to the
$wtf variable in the signature, it has no idea it's in a signature of an
actual code and not just some random text, so it cries about the variable
being undeclared.

Won't Someone Think of The Reader?

Here's a great error that prevents you from writing horrid code:

my $a;
sub {
$a.say;
$^a.say;
}
# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# $a has already been used as a non-placeholder in the surrounding sub block,
# so you will confuse the reader if you suddenly declare $^a here
# at /home/zoffix/test.p6:4
# ------> $^a⏏.say;

Here's a bit of a background: you can use the
$^ twigil
on variables to create an implicit signature. To make it possible to use such
variables in nested blocks, this syntax actually creates the same variable
without the twigil, so $^a and $a are the same thing, and the signature
of the sub above is ($a).

In our code, we also have an $a in outer scope and supposedly we print it
first, before using the $^ twigil to create another $a in the same scope,
but one that contains the argument to the sub... complete brain-screw! To
avoid this, just rename your variables to something that doesn't clash. How
about some Thai?

Well, Colour Me Errpressed!

If your terminal supports it, the compiler will emit ANSI codes to colourize
the output a bit:

for ^5 {
say meow";
}

That's all nice and spiffy, but if you're, say, capturing output from the
compiler to display it elsewhere, you may get the ANSI code as is, like
31m===[0mSORRY![31m===[0m.

That's awful, but luckily, it's easy to disable the colours: just set
RAKUDO_ERROR_COLOR environmental variable to 0:

You can set it from within the program too. You just have to do it early
enough, so put it somewhere at the start of the program and use the
BEGIN phaser to set it
as soon as the assignment is compiled:

BEGIN %*ENV<RAKUDO_ERROR_COLOR> = 0;
for ^5 {
say meow";
}

An Exceptional Failure

Perl 6 has a special exception—Failure—that
doesn't explode until you try to use it as a value, and you can even defuse it
entirely by using it in boolean context. You can produce your own Failures
by calling the fail subroutine and
Perl 6 uses them in core whenever it can.

Here's a piece of code where we define a prefix operator for calculating the
circumference of an object, given its radius. If the radius is negative,
it calls fail, returning a Failure object:

We're calculating the circumference for a negative radius on line 7, so if
it were just a regular exception, our code would die there and then. Instead,
by the output we can see that we continue to calculate the circumference
of the Earth and the Sun, until we get to the last line.

There we try to use the Failure in $cₘ variable as one of the arguments to
the max routine. Since we're asking for
the actual value, the Failure explodes and gives us a nice backtrace. The
error message includes the point where our Failure blew up (line 15), where
we received it (line 7) as well as where it came from (line 2). Pretty sweet!

Conclusion

Useful, descriptive errors are becoming the industry standard and
Perl 6 and Rust languages
are leading that effort. The errors must go beyond merely telling you the
line number to look at. They should point to a piece of code you wrote.
They should make a guess at what you meant. They should be referencing your
code, even if they originate in some third party library you're using.

Most of Perl 6 errors display the piece of code containing the error. They
use algorithms to offer valid suggestions when you mistyped a subroutine name.
If you're used to other languages, Perl 6 will detect your "accent," and offer
the correct way to pronounce your code in Perl 6. And instead of immediately
blowing up, Perl 6 offers a mechanism to propagate errors right to the code the
programmer is writing.