Sunday, 16 December 2007

Python decorators in Lisp, Part 1

In some version of Python the community reached a consensus that decorators were a useful addition to the language. Decorators were implemented to encapsulate function transformation which usually took the following form:


def synchronized(lock):
"""Return a decorator that ensures the decorated function is only called when
holding lock"""
def decorator(fn):
def the_fn(*args,**kwargs):
get_lock(lock)
try:
return fn(*args,**kwargs)
finally:
release_lock(lock)
return the_fn
return decorator

def foo(bar):
zonk(bar)
foo = synchronized(lock)(foo)

In the above example, the function foo is modified to ensure it holds a lock first before calling zonk(bar). Unfortunately, this transformation has very poor placement in terms of readability. If foo was very long, it would get lost in the noise as it is at the end. So a new syntax was proposed:

def synchronized(lock);
# as before

@synchronized(lock)
def foo(bar):
zonk(bar)

That placement is a lot better and once you understand what decorators are for, it is a lot more readable than the alternative.

I was reading through some Django code the other day (Reviewboard) and noticed that they were very heavy on usage of decorators. It is quite a handy tool it seems. So I got envious. I wondered why Lisp did not have this functionality. Is it not possible? Do you need to meet for months to put this into the Common Lisp standard? Thankfully, the answer is no. I will cover how I arrived at this in Part 2, perhaps later today, but here is the equivalent Lisp code:

(defun synchronized (lock)
(lambda (fn)
(lambda (&rest args)
(with-lock lock
(apply fn args)))))

#@(synchronized lock)
(defun foo (bar)
(zonk bar))

I should also mention that it is more flexible than the Python equivalent:

#@(lambda (fn) (lambda (&rest args) (print "lalalalalal") (apply fn args)))
(defun foo bar (bar)
(zonk bar))

Not that there is actually any use for it besides "I should also mention" purposes.

Pretty neat eh?

An interesting quote from PEP 318:

It's not yet certain that class decorators will be incorporated into the language at a future point. Guido expressed skepticism about the concept, but various people have made some strong arguments [28] (search for PEP 318 -- posting draft) on their behalf in python-dev. It's exceedingly unlikely that class decorators will be in Python 2.4.

Thank goodness Guido will not stand in my way ;-)

Edit: I am fully aware that this is not a Lisp idiom. I just wanted to see if it could be done, more than anything.

Edit: I have added part 2 here.

2 comments:

Robert said...

This facility seems quite similar to the old Lisp advice facility, at least as described here. Gary King has been blogging about it pretty extensively.

Advice is used pretty heavily in emacs-lisp, and Franz offers a better-than-advice (but non-portable) construct called fwrappers.

Sohail Somani said...

I think its slightly different from the advice facility in that the decorators only get called once.

A decorators domain/range is functions to functions so you could implement before, after and around methods. However, that isn't the only use. Imagine registering the passed in function to be called on exit (stolen from the Python examples):

#@onexit
(defun last-breath () ... )

I am actually just looking for a way to specify authentication requirements for a web page:

#@(allow :Administrator)
(defun admin-page () ... )

A more Lispy way may be to use advice. Thanks for pointing it out :-)