The Catalyst web framework uses
Perl 5 function
attributes effectively—I've seen few more effective uses of
Any modern web framework has to deal with the idea of routes and request
routing somehow. Given a request path (such as /stocks/AA/view_analysis),
how does your application know what to do?
Catalyst solves this elegantly with a feature known as chained actions.
Controller methods can consume zero or more parts of the path but, when
explicitly chained, can combine. Consider the example request path. The
controller is Stocks.pm. The second component of the path
(/AA) is the identifier for a stock (Alcoa, to be specific. I'm
neither long nor short on Alcoa itself, though I probably own some shares as
part of a fund somewhere.) The final component of the path,
/view_analysis, is an action—a verb representing an action the
controller should take on the object representing Alcoa in the system.
You can probably start to see the idea of the chain right away.
The Stock controller has a controller method called get_stock which
grabs the stock symbol from the request path, looks it up in the database, and
stores the object representing that stock for further processing. If no such
symbol exists, it throws an exception.
view_analysis method chains off of the get_stock
method such that Catalyst will only dispatch to
it's already successfully dispatched to get_stock. Unless you write a
custom dispatch system which bypasses the dispatch rules, users will never be
able to call
view_analysis without a valid stock object
(Further, these methods are part of a chain which requires that users have
successfully logged into the system; they chain off of a user authentication
In code terms, the relevant attributes look something like:
sub authorized :Chained('/login/required') :PathPart('stocks') :CaptureArgs(0);
sub get_stock :Chained('authorized') :PathPart('') :CaptureArgs(1);
sub view_analysis :Chained('get_stock') :PathPart('view_analysis') :Args(0);
:Chained attribute is most relevant here.
:PathPart governs how Catalyst's dispatcher makes each method
visible to user requests (
get_stock doesn't consume a part of the
path on its own, while
authorized consumes the name of the
view_analysis consumes its own name).
:Args control how many other pieces
of the path the methods consume; in the case of
the single path element between
/stocks and any subsequent chained
actions—in this case,
the end point of a chain, you use
:Args instead of
With that all explained, request method chaining is fantastic. I can reuse
get_stock() for other request methods and get all of its benefits,
including the fact that only authorized users can even reach this point.
Yet I want to prove these characteristics of my application.
I want to prove these features so definitively that I don't want to write
tests for them. I want my program to fail to compile if these
characteristics are untrue.
I see chaining from
get_stock() as supplying an invariant
view_analysis() such that it proves, to my
satisfaction, that I can always rely on a valid stock object being available
within the analysis method. Always. Similarly, I can always rely on a valid
user being available within both methods. Always always.
The problem comes in that it's easy to make a typo in the name of a chain or
a method, or to use
:CaptureArgs instead of
Here's the thing: all of this metadata is metadata. All of this information
is available at compile time, before Perl has to execute anything.
If I had a really good and extensible type system in Perl 5, I could write a
couple of pieces of predicate logic to say that every chained method should be
a starting point or have a valid predecessor. These are trivial properties of
my program (no matter how large it gets) and they're resolvable with the
information available at the point of compilation. Even with complex controller
construction through the use of roles and parametric roles, this information is
I know how to emulate this behavior by injecting some sort of
CHECK block into the code and schlepping through the symbol table
and inspecting attributes myself, but that's emulating a useful feature we
could exploit in a lot of ways.
Forget the talk about making Perl into Java or C++ by adding a silly
manifest static type system. We could find and fix real errors in
logic—trivial errors, trivially discoverable—if we had an
extensible type system which let us define our own simple predicates.
(Implementing such is left as an exercise for a small army of readers cloned
from a very small army of brilliant p5p hackers with copious spare time and a
habit of reading ACM papers before breakfast.)