Flickr Badge

Wednesday, May 13, 2009

Pattern Matching With PEAK-Rules

I came across this interesting article on Pattern matching in Python. The article asks: Can we recreate in Python the pattern matching semantics present in languages like Haskell and Erlang.

For those not familiar with the way pattern matching works, I've copied the example from the article above.
%% handle(Path, Method)
handle("/", _) ->
not_a_resource;
handle(Path, 'PUT') ->
create_new_resource(Path);
handle(Path, 'POST') ->
update_resource(Path);
handle(Path, 'GET') ->
retrieve_resource(Path);
handle(_, _) ->
invalid_request.
What the above code does is to return "not a resource" if you call the handle function with the path parameter as "/" and any method. If you call handle with any path and method parameter as "GET" then it calls retrieve_resource(Path) and so on.

PEAK-Rules

A method to replicate this in Python was given using match objects, but I thought hey why go through all this trouble when PEAK-Rules does most of this for us?

Multimethod Dispatch

PEAK-Rules is a library that enables multimethod dispatch in Python. Those with an OO background will recognise a specific instance of this in method overloading. When an overloaded method is called, execution can go to different method implementations depending upon the type of the parameters passed. In generic multimethod dispatch, you can route execution based on any criteria that you define. PEAK-Rules brings this sort of generic multimethod dispatch to Python.

An Example

So lets take a concrete example. Our goal is to rewrite the above Erlang code in Python using two libraries: PEAK-Rules and an add-on called prioritized_methods that allows prioritised method ordering.

First, you'll need to get install PEAK-Rules and prioritized_methods. You can pick them up from the links given or if you've got setuptools, then you can just easy_install them.

Then type out the following code:
>>> from peak.rules import abstract, when
>>> from prioritized_methods import prioritized_when
>>> @abstract()
... def handle(path, method):
... pass
...
>>> @prioritized_when(handle, "path == '/'", prio=1)
... def not_a_resource(path, method):
... print "not a resource"
...
>>> @when(handle, "method == 'GET'")
... def get_resource(path, method):
... print "getting", path
...
>>> @when(handle, "method == 'PUT'")
... def create_resource(path, method):
... print "creating", path
...
>>> @when(handle, "method == 'POST'")
... def update_resource(path, method):
... print "updating", path
...
Here is what is happening:

We first define an abstract function handle(path, method). Do this by placing the @abstract() decorator on it.

Now consider this snippet:
>>> @when(handle, "method == 'GET'")
... def get_resource(path, method):
... print "getting", path
...
This defines one implementation for the handle function. It says, when the handle function is called, and the condition given is True (in this case method == "GET"), then call the implementation given below (here: the get_resource function).

Similarly we define the other implementations to be called on some other conditions.

The only thing left is the usage of @prioritized_when. Now, when a call is made to handle("/", "GET"), we see that the condition for not_a_resource as well as get_resource are satisfied. Which implementation should be called? In this case, we use the @prioritized_when decorator and set the priority to 1. This tells the system to give priority to this implementation in case of conflict in match.

Here is how the output looks:
>>> handle("/", "GET")
not a resource
>>> handle("/", "POST")
not a resource
>>> handle("/home", "PUT")
creating /home
>>> handle("/home", "POST")
updating /home
>>> handle("/home", "GET")
getting /home
Pretty cool! The best part of this is that you can dispatch on virtually any condition. While the resulting code is a little more verbose than the Erlang example, its not too bad and it does the job well.