Wednesday, September 12, 2018

Yadda: How should exceptions and abstract methods be combined?

Perl 6 is many things, but I have always felt that its most important role is in exposing other languages to modern concepts in programming language design, in a more real-world environment than many research languages that tend not to be shaped by actual production use, the likes of which Perl has seen for decades, now.

One of those areas where modern concepts have influenced Perl 6 is the use of the "yadda" operator. At first, this was only in the form of the ellipsis (three periods, if you are not using a Unicode-enhanced editor, or just prefer ASCII equivalents). This operator is used in situations where an unstated piece of code is suggested, but not implemented.

In the most common use-case, this is because the compiler is expected to intuit the appropriate code by assessing a list of values and providing a generator that will continue the sequence, for example:

1,2,3 ... 100
This sequence is the same as the explicit range (note the double-dot range operator):

1 .. 100
But the compiler can intuit several different kinds of successor function. This article is not about that usage, though; it is instead about the use of the yadda operator in abstract method definitions, as such:

class SomeThing {

    # Sub-class should define this:
    method do-stuff { ... }

}
In this case, we are defining an abstract base-class called "SomeThing" which contains a method called "do-stuff". But the do-stuff method is not defined. The yadda operator tells the compiler that, should a programmer attempt to call do-stuff directly, without going through a subclass that defines it properly, an exception should be packaged and returned (see below).

This is common in many OO languages, but Perl 6 takes this one step further, adding two additional operators which differ only in how exceptions are handled:

class SomeThing {
    method do-stuff { ... } # fail() is called when executed
    method do-other-stuff { ??? } # warning is generated
    method do-new-stuff { !!! } # die() is called when executed
}
The difference is in how exception-handling is managed. In the first case, a call to the abstract method returns a "failure" which is something like an unexploded bomb. It is intended to improve debugging and error-reporting by identifying both the offending method invocation and the use of its returned value. It does this by wrapping an exception object in a semi-safe failure object that is benign unless or until its value is examined, and then a new exception with both contexts is raised, and the compiler can present a detailed history of the offending code-paths to the user.

If you desire a more straightforward approach, and/or are writing an abstract method where any access would be problematic immediately, you can use the !!! form, which immediately raises an exception upon use.

On the other hand, if you are writing a method whose use might be acceptable (e.g. replacing an obsolete API where an old method becomes irrelevant) you can use the ??? form and a warning will be generated, which the caller can suppress if needed.

These lessons are important for other languages to consider, not just in a "that's not the way we do it," sense, but in a serious way. Language design is an ever-changing landscape, and any language that gets too entrenched in its original design is measuring its own mortality. One way to avoid such long-term failure is to evaluate new ideas, absorb what works and reject what does not.

Perl 6 offers many opportunities to perform such evaluations (as I've covered previously) and language designers and maintainers would be remiss in not availing themselves of the opportunity to learn from such ambition.