Esa-Matti Suuronen

I write code and jump

Python-like Decorators in CoffeeScript

I really like decorators in Python and I sometimes miss them when working in other languages. Today at work it hit me when I was working on a CoffeeScript project. It is really easy to implement Python-like decorators cleanly in CoffeeScript.

If you are not familar what decorators are in Python you should skim through this and this. In short they are a nice syntax for wrapping functions/methods with other functions in Python.

Decorators in Python

Here’s an example usage of Python decorator. Let’s pretend that this is a class for reading values from some device. It will give us values between 0 and 100, but in this app we want put a roof for the values it gives. We can create a decorator that limits the values given by the getter.

Decorator in Python (decorator_example.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python

# Our magic device
import random

def roof(amount):
    def decorator(method):
        def wrapper(*args):

            value = method(*args)
            if value > amount:
                return amount
            else:
                return value

        return wrapper
    return decorator


class Device(object):

    @roof(50)
    def get_value(self):
        # Read value from the device
        return random.randint(0, 100)


if __name__ == '__main__':
    reader = Device()
    for i in range(10):
        print reader.get_value()

Decorators in CoffeeScript

So that was an advanced configurable decorator for Python. Lets see how CoffeeScript handles the same situation.

Decorator in CoffeeScript [] (decorator_example.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env coffee

roof = (amount) -> (method) -> ->

  value = method.apply @, arguments

  if value > amount
    amount
  else
    value

class Device

    getValue: roof(50) ->
      parseInt Math.random() * 100


if require.main is module
  device = new Device
  for i in [0..10]
    console.log device.getValue()

Wow! That is a lot less syntax and no extra nesting!

This really shows how powerful anonymous functions and implicit returns are in CoffeeScript. Also the usage syntax would not be so clean if CoffeeScript didn’t have ability to call functions without the parenthesis.

The usage syntax is though better in Python, because you can stack decorators cleanly with it.

Stacking decorators in Python
1
2
3
4
5
class Device(object):
    @roof(50)
    @floor(10) # Checks the bottom of the value
    def get_value(self):
        return random.randint(0, 100)

In CoffeeScript must put them after each others which can get nasty if you have many decorators.

Piping decorators in CoffeeScript
1
2
3
class Device
    getValue: roof(50) floor(10) ->
      parseInt Math.random() * 100

But wait! There were no specific decorator syntax in Python in the old days. One could apply decorators just by calling it to the target and replacing the original method.

Oldschool decorator usage
1
2
3
4
5
6
class Device(object):
    def get_value(self):
        return random.randint(0, 100)

    get_value = roof(50)(get_value)
    get_value = floor(10)(get_value)

So you can do this in CoffeeScript

Piping decorators in CoffeeScript
1
2
3
4
5
6
class Device
  getValue: roof(50) ->
    parseInt Math.random() * 100

  getValue: roof(50) Device::getValue
  getValue: floor(10) Device::getValue

Pretty ugly, yeah, but might be better if you have tons of decorators.

Comments