If We Could Resolve Predicates at Compile Time

| 3 Comments

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 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.

The view_analysis method chains off of the get_stock method such that Catalyst will only dispatch to view_analysis when 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 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.)

3 Comments

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.)

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on December 12, 2011 10:27 AM.

Track App Progress with Writeable $0 was the previous entry in this blog.

When Print Debugging Fails is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?