Decorators
Suppose we've got a slow program. The first step to making it faster is figuring out where the CPU is spending its time. Perhaps we could count how many times each method is called or measure how much time is spent in each. A naive approach to auditing these calls would be to define some global tracking data and inject code in each method to update the data. But if we have many methods to audit, we want a shortcut. Instead of modifying them, we want to wrap them in a decorator function. A decorator is a higher-order function that accepts another function as a parameter, adds some behavior to each call, and returns a new function meant to replace the original.
Here's a decorator that counts how many times its parameter function f is called:
function count(f) {
function wrapper(...args) {
wrapper.count += 1;
const result = f(...args);
return result;
}
wrapper.count = 0;
return wrapper;
}
function count(f) {
function wrapper(...args) {
wrapper.count += 1;
const result = f(...args);
return result;
}
wrapper.count = 0;
return wrapper;
}The nested wrapper function increments the count that it carries around and then calls the original function f. Its actual parameters arrive packed in an array because of the rest parameter syntax. On the call to f, the array is unpacked back to a parameter sequence by the spread operator. This packing and unpacking makes it so count can be applied to functions of arbitrary arity.
Suppose we have this Worker class whose compute method we wish to audit:
class Worker {
compute() {}
}
const worker = new Worker();
class Worker {
compute() {}
}
const worker = new Worker();We could manually decorate compute with this explicit redefinition:
worker.compute = count(worker.compute);
worker.compute = count(worker.compute);
But there's an easier syntax using an annotation:
class Worker {
@count
compute() {}
}
class Worker {
@count
compute() {}
}The JavaScript interpreter will automatically replace the compute method within its decorated version.
We call the decorated version just as we would the original method:
worker.compute();
worker.compute();
worker.compute();
console.log(worker.compute.count); // prints 3
worker.compute(); worker.compute(); worker.compute(); console.log(worker.compute.count); // prints 3
Python supports decorators with a very similar syntax. Ruby doesn't have the annotation syntax but does support explicit wrapping.
Decorators are commonly found in modern web development libraries. Clients write their own functions but then augment them with extra features provided by the library, like logging or routing from a URL.