The Catalyst web framework uses Perl 5 function attributes effectively—I've seen few more effective uses of attributes.
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
The view_analysis
method chains off of the view_analysis
when
it's already successfully dispatched to view_analysis
without a valid stock object
available.
(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 system.)
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);
The :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
controller and view_analysis
consumes its own name).
:CaptureArgs
and :Args
control how many other pieces
of the path the methods consume; in the case of get_stock
, it's
the single path element between /stocks
and any subsequent chained
actions—in this case, /AA
. As view_analysis
is
the end point of a chain, you use :Args
instead of
:CaptureArgs
.
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
precondition to 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 :Args
or
vice versa.
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 available.
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.)
Except that view_analysis offends my HTTP sensibilities, and I’d PathPart('analysis') that.
And even calling the method itself view_analysis is a hint to me that you aren’t using ActionClass('REST'). Which you should.
You caught me. I have the bad habit of using verbs for method names and wanting those method names to map to URI components. analysis is clearly better from the RESTful perspective.
No, from the URI design perspective, if I may be a pedant once more. :-)
(All REST has to say about URIs is that clients are never to parse or construct them using off-the-wire knowledge, only follow them, so /?foo=as.ldkj93j;bar=ps9ah19 is as much a “RESTful URI” for the stock analysis page as /stock/AA/analysis.)