tl;dr Function decorators are a pattern for extracting reusable “ideas” from your code. This document describes how to write a bookend (“before-and-after”) decorator in CoffeeScript that works with callbacks (instead of return values).
Let’s say that you have a critical section of code and you want to use a mutex to lock the code. In CoffeeScript you might write something like this:
criticalSectionA = () ->puts"critical section A""Apple"acquireLock = (name) ->puts"locked #{name}"# <- e.g. some locking functionreleaseLock = (name) ->puts"released #{name}"# <- e.g. some release functiondoFooA = () ->acquireLock("A")value = criticalSectionA()releaseLock("A")value
Running:
puts doFooA()
Outputs:
locked A
critical section A
released A
Apple
Here we acquire the lock, run our critical section, release the lock, and we have our final value Apple. Simple enough.
Now let’s say you need to lock another section of code:
Hmm. If we compare doFooB to doFooA we can see that they are definitely doing different things:
We’re acquiring a different lock ("B" vs. "A") and
We’re calling a different function for the critical section (criticalSectionB vs. criticalSectionA)
But despite their differences on the surface, they’re very similar conceptually: every time we acquire a lock we need to release it when we’re finished using it.
Implementing locking this way smells funny in two ways:
Our locking logic is intertwined with our function’s logic.
We have to explicitly store the value of the critical section and remember to return it after we release the lock.
Rephrased in a positive light, we’d like to:
Have the locking logic separate from our function’s core purpose (let’s call it the “business logic“) and
Encapsulate the idea of locking without having to remember the cleanup steps every time we try to use it
Identifying “Aroundness”
Locking isn’t the only situation where we have this pattern. We often need to perform a cleanup step such as closing files, closing sockets, release a resource from a pool, etc.
What we need is a way to specify “aroundness” that we can reuse anytime we encounter the pattern “initialize > do something > cleanup”. Decorators provide a way to do this.
Decorators are functions that take other functions as their arguments and add new behaviors to those functions. The idea is that the function returned from the decorator can masquerade as the original function, but now it has super powers.
Let’s use Reg’s around function to write our locking decorator.
around is a function which takes the decoration (also a function) as its only argument. The decoration itself takes one argument: cb which “represents” our original function (the business logic). The intuition here is that our business logic will be run when we invoke cb.
In our case, we will invoke our setup (i.e. acquiring a lock) before cb and invoke our cleanup (i.e. releasing a lock) after cb.
lockingA is now our decorator and we can use lockingA to wrap any function with our locking logic.
We use lockingA by invoking it and passing a base function (our business logic) as the argument.
doFooADecorated = lockingA() ->criticalSectionA()
Now let’s run doFooADecorated()
puts doFooADecorated()
This outputs:
locked A
critical section A
released A
Apple
Great! These two functions feel much cleaner than what we had before. We have a clear separation of concerns between the two functions, it’s easy to read, and if we change our locking implementation we need only to change one function.
Clean Up the Decorator
I’m sure you can see one major shortcoming in the previous example: Our decorator lockingA only works if you want to lock "A". Ideally, we’d like to be able to use this same decorator with a lock of any name.
We can do this by wrapping the decorator itself in a function:
In doFooA we assign the result of criticalSectionA to value and return value at the end of the function. In doFooADecorated even though we’re using the around decorator, under the hood we’re still just storing the return value of our business logic (as you can see in the implementation of around here).
However, when we’re doing async code, we typically don’t return a value. Instead, we invoke a callback function send whatever values we need as arguments to that callback.
Let’s put this in concrete terms to see where we run into trouble. Using callbacks our criticalSectionA would probably look like this:
puts "before lock"
doFooADecorated (result) ->
puts "unlocked?"
puts result
This outputs:
before lock
locked A
critical section A
unlocked?
Apple
released A
Well that isn’t quite right. Our lock wasn’t released when we expected. Instead, our lock “leaked out” of doFooADecorated and into the rest of our code.
But the situation gets worse before it gets better…
Beware Next Tick
In node.js we have the concept of process.nextTick which defers execution of the current function (this is very common in node libraries e.g. mikeal/request).
Say, for example, that our critical section calls some code that uses process.nextTick.
before lock
locked A
released A
critical section NT
Apple
Egads! This time our lock didn’t leak out — it was released before we called the critical section!
What’s happening here is that in criticalSectionNT when we call process.nextTick execution on that function is deferred and, as a result, the lock is released before we’ve called our critical section.
Revisiting Aroundness
To fix this, we need to revisit what the concept of “aroundness” means when we’re working with callbacks. Let’s get rid of all the nested functions and look at an async doSomething and write the locking logic directly in the function.
It works, but it has all of the shortcomings we’re trying to eliminate in the first place.
Locking with Callbacks
So let’s try to extract the locking logic from the business logic. It’s helpful to think through what we want the interface to be before we start on the implementation.
Ideally we’d like
doSomething to contain only business logic and
The locking decorator to be a simple “declaration” at the beginning of the function.
E.g. something like: doSomething = locking (done) -> ...
To implement this we have to ask the question: what is “aroundness” when you’re working with callbacks anyway? If you look at the previous implementation of doSomething you can see that we are doing two things:
acquireLock at the beginning of doSomething (i.e. beforedoSomething)
releaseLock right before we invoke the callback done (i.e. afterdoSomething)
Implementing the before step is straightforward, but understanding the after step is a little trickier.
In an asyncronous context, in order to call something “after” a function we have to change the callback argument and inject our after function into the new callback.
In lockingA we accept a base function (our business logic) and we return a new function that will masquerade as base.
The base function also takes arguments, but we don’t know what they are, so we capture them all with argv.... However, since the base function is asyncronous, by convention, the callback is the last parameter in the argument list (and that convention becomes a requirement with our decorator).
So we create a new callback that contains the unlocking logic and replace origCallback with our new callback when we invoke base.
Our decorator is still too specific to locking. We still need to extract the ability to have generic before and after logic.
Looking closely at our current version of lockingA we see that there are only two lines specific to locking (acquireLock/releaseLock)
Thinking about the interface again, we’d like to define lockingA by specifying only the locking logic. This function is going to need to two other functions as its arguments. Something like:
There’s one remaining problem with our lockingA decorator: just like last time, it only works for lock "A". We can solve that by simply wrapping our call to bookend in a function itself:
What we’ve written here is extremely useful in maintaining separation of concerns in our code. But there are still two important questions we need to answer:
What if we want to decorate something with a before only or an after only?
What if our before or after decorations are async themselves?
Callback-based decorations
Let’s throw away our decorators again and start with the obvious way to write our example. Only this time let’s make acquireLockCb and releaseLockCb which are asynchronous versions of our locking/unlocking code.
We’ve gone from a function with locking logic threaded throughout to creating a higher-order decorator we can use to capture reusable ideas.
Reg has added async.before, async.after, and async.provided to method-combinators. Try using method-combinators in your next project and let me know how it goes!
Asynchronous Function Decorators
Introduction
Let’s say that you have a critical section of code and you want to use a mutex to lock the code. In CoffeeScript you might write something like this:
Running:
Outputs:
Here we acquire the lock, run our critical section, release the lock, and we have our final value
Apple. Simple enough.Now let’s say you need to lock another section of code:
Hmm. If we compare
doFooBtodoFooAwe can see that they are definitely doing different things:"B"vs."A") andcriticalSectionBvs.criticalSectionA)But despite their differences on the surface, they’re very similar conceptually: every time we acquire a lock we need to release it when we’re finished using it.
Implementing locking this way smells funny in two ways:
valueof the critical section and remember to return it after we release the lock.Rephrased in a positive light, we’d like to:
Identifying “Aroundness”
Locking isn’t the only situation where we have this pattern. We often need to perform a cleanup step such as closing files, closing sockets, release a resource from a pool, etc.
What we need is a way to specify “aroundness” that we can reuse anytime we encounter the pattern “initialize > do something > cleanup”. Decorators provide a way to do this.
Decorators are functions that take other functions as their arguments and add new behaviors to those functions. The idea is that the function returned from the decorator can masquerade as the original function, but now it has super powers.
A Locking Decorator
Reg Braithwaite has written a wonderful article on decorators in CoffeeScript. In that article, he describes how to write various decorators such as
before,after, andaround.Let’s use Reg’s
aroundfunction to write our locking decorator.aroundis a function which takes the decoration (also a function) as its only argument. The decoration itself takes one argument:cbwhich “represents” our original function (the business logic). The intuition here is that our business logic will be run when we invokecb.In our case, we will invoke our setup (i.e. acquiring a lock) before
cband invoke our cleanup (i.e. releasing a lock) aftercb.lockingAis now our decorator and we can uselockingAto wrap any function with our locking logic.We use
lockingAby invoking it and passing a base function (our business logic) as the argument.Now let’s run
doFooADecorated()This outputs:
Great! These two functions feel much cleaner than what we had before. We have a clear separation of concerns between the two functions, it’s easy to read, and if we change our locking implementation we need only to change one function.
Clean Up the Decorator
I’m sure you can see one major shortcoming in the previous example: Our decorator
lockingAonly works if you want to lock"A". Ideally, we’d like to be able to use this same decorator with a lock of any name.We can do this by wrapping the decorator itself in a function:
Let’s try running these functions:
Which outputs:
Aroundness in a Callback’s World
There are a few problems with our current implementation of
lockthat show up when you try to use it with callbacks.Remember our first attempt at implementing locking?
In
doFooAwe assign the result ofcriticalSectionAtovalueand returnvalueat the end of the function. IndoFooADecoratedeven though we’re using thearounddecorator, under the hood we’re still just storing the return value of our business logic (as you can see in the implementation ofaroundhere).However, when we’re doing async code, we typically don’t
returna value. Instead, we invoke a callback function send whatever values we need as arguments to that callback.Let’s put this in concrete terms to see where we run into trouble. Using callbacks our
criticalSectionAwould probably look like this:If we weren’t aware of how
aroundwas implemented, our first instinct for writingcriticalSectionAmight be to write it like we did last time:Now we decorate our function with
lockAAnd run it:
This outputs:
Well that isn’t quite right. Our lock wasn’t released when we expected. Instead, our lock “leaked out” of
doFooADecoratedand into the rest of our code.But the situation gets worse before it gets better…
Beware Next Tick
In node.js we have the concept of
process.nextTickwhich defers execution of the current function (this is very common in node libraries e.g.mikeal/request).Say, for example, that our critical section calls some code that uses
process.nextTick.Run it:
Outputs:
Egads! This time our lock didn’t leak out — it was released before we called the critical section!
What’s happening here is that in
criticalSectionNTwhen we callprocess.nextTickexecution on that function is deferred and, as a result, the lock is released before we’ve called our critical section.Revisiting Aroundness
To fix this, we need to revisit what the concept of “aroundness” means when we’re working with callbacks. Let’s get rid of all the nested functions and look at an async
doSomethingand write the locking logic directly in the function.Run it:
Outputs:
It works, but it has all of the shortcomings we’re trying to eliminate in the first place.
Locking with Callbacks
So let’s try to extract the locking logic from the business logic. It’s helpful to think through what we want the interface to be before we start on the implementation.
Ideally we’d like
doSomethingto contain only business logic andE.g. something like:
doSomething = locking (done) -> ...To implement this we have to ask the question: what is “aroundness” when you’re working with callbacks anyway? If you look at the previous implementation of
doSomethingyou can see that we are doing two things:acquireLockat the beginning ofdoSomething(i.e. beforedoSomething)releaseLockright before we invoke the callbackdone(i.e. afterdoSomething)Implementing the before step is straightforward, but understanding the after step is a little trickier.
In an asyncronous context, in order to call something “after” a function we have to change the callback argument and inject our after function into the new callback.
In
lockingAwe accept abasefunction (our business logic) and we return a new function that will masquerade asbase.The
basefunction also takes arguments, but we don’t know what they are, so we capture them all withargv.... However, since thebasefunction is asyncronous, by convention, the callback is the last parameter in the argument list (and that convention becomes a requirement with our decorator).So we create a new
callbackthat contains the unlocking logic and replaceorigCallbackwith our newcallbackwhen we invokebase.Run it:
Outputs:
It works!
The Bookend Decorator
Our decorator is still too specific to locking. We still need to extract the ability to have generic before and after logic.
Looking closely at our current version of
lockingAwe see that there are only two lines specific to locking (acquireLock/releaseLock)Thinking about the interface again, we’d like to define
lockingAby specifying only the locking logic. This function is going to need to two other functions as its arguments. Something like:Let’s abstract
lockingAinto a new functionbookendby doing the following:beforeandafteras argumentsbeforebefore we call the base functionafterbefore we call the old callbackNow we can define our decorator
lockingA:and create a function which we decorate with
lockingARun it:
Outputs:
Parametrizing the Decorator
There’s one remaining problem with our
lockingAdecorator: just like last time, it only works for lock"A". We can solve that by simply wrapping our call tobookendin a function itself:Now we can decorate – with parameters!
Run it:
Outputs:
Success!
What we’ve written here is extremely useful in maintaining separation of concerns in our code. But there are still two important questions we need to answer:
Callback-based decorations
Let’s throw away our decorators again and start with the obvious way to write our example. Only this time let’s make
acquireLockCbandreleaseLockCbwhich are asynchronous versions of our locking/unlocking code.Run it:
This outputs:
Now let’s extract
beforeandafter, one step at a time.Extracting Async Before
Before jumping right into a generic
beforedecorator, let’s first write a decorator that does only locking and ignores the unlocking.Run it:
Outputs:
Now we need to pull
acquireLockCbout intobeforeCb.Run it:
Outputs:
Extracting Async After
Let’s temporarily discard the
beforedecorator so we can see theafterdecorator more clearly.Run it:
Outputs:
Now let’s generalize the unlocking into an after decorator:
To make this function comprehensible, it might help to read the description of this function out-loud:
afterCbis a function which takes a decoration and returns a decoratorbasefunctionbasefunction takes any number of arguments with a callback as the last argumentnewCallbackis created which calls the decoration before it invokes the original callbackbasefunction is invoked with thisnewCallbackRun it:
Outputs:
A neat feature about these decorators is that we can chain them!
A New Bookend Combinator
Because we can chain
beforeCbandafterCb, writing a bookend decorator becomes trivial:Summary
We’ve gone from a function with locking logic threaded throughout to creating a higher-order decorator we can use to capture reusable ideas.
Reg has added
async.before,async.after, andasync.providedtomethod-combinators. Try usingmethod-combinatorsin your next project and let me know how it goes!References
raganwald / method-combinatorsThanks to Reg Braithwaite, Devin Foley, Moises Beltran, and Rob Zinkov for reading drafts of this.
If you enjoyed this article then you should follow me on twitter where I discuss higher-order software design, bioluminescence, and beekeeping.