Decorated functions in Go
Posted on Fri 04 September 2015 in Code
One of the downsides of working with a simple and minimalistic programming language such as Go is that abstracting for code reuse is often rather challenging. What’s brought up most often in this context is Go’s lack of support for generics (higher order types).
But here I wanted to talk about a different mechanism that — unlike generics — can be transplanted to Go quite successfully: function decorators.
Canonical example
Decorators are most commonly found in Python (where they can be applied not just to functions, but also classes), but have syntactical analogues in Java (annotations), C# (attributes), and a few other languages.
A decorator looks like a modifier adorning a function definition, distinguished by its initial @
sign:
@app.route('/home')
@login_required
def home():
return render_template('user/home.html')
Even if you’re not familiar with the particular web framework this request handler is meant for — or indeed, the Python language itself — it shouldn’t be too difficult to figure out what purpose the two decorators serve.
What’s really important is that their goals are neatly separated from essential logic of the request handler:
- it doesn’t have to explicitly check if the user is logged in — it has the
@login_required
decorator - it doesn’t have to be separately registered in some
centalized URL routing choke point —
it has the
@app.route
decorator instead
Such separation of concerns is a desirable quality in software, because it makes it easier to reason about different aspects of the system.
Under the hood
Behind this synctactic sugar and lofty phrasing, the code above is still perfectly equivalent to its undecorated version:
def home():
if not current_user.is_authenticated:
abort(403, "Login Required")
return render_template('user/home.html')
app.add_url_route('/home', view_func=home)
No source code transformations take place, either: @login_required
doesn’t actually “inject” the if
statement
at the beginning of home
function. What happens instead is that it takes the whole function and wraps it
in a new one which also contains the crucial check:
def login_required(func):
"""Grossly simplified version of a decorator enforcing user login."""
def wrapped(*args, **kwargs):
if not current_user.is_authenticated:
abort(403, "Login Required")
return func(*args, **kwargs)
return wrapped
Given this definition, decorating home
simply means passing it to the login_required
function, and calling its result
a new home
:
def home():
return render_template('user/home.html')
home = login_required(home)
As you’ve probably guessed, the @
syntax is nothing else than a syntatic sugar for the above.
But how sweet a sugar it is!
(The @app.route
decorator is simultaneously simpler and more complicated. I recommend having a direct look at
its source code
for more insight).
What gophers can learn
Unlike Python, in Go, we are out of luck when it comes to dedicated language support for decorators. However, the general principle still applies: we can decorate functions by writing — ahem — decorator functions.
Consider, for example, a trivial net/http
request handler:
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, world!\n")
}
Any real web application will have many similar handlers. They are usually wired to their corresponding URL paths during program startup:
func main() {
http.HandleFunc("/hello", hello)
// ...
}
This approach to routing configuration provides an opportunity to decorate some (or all) of the request handlers with any auxiliary functionality that the application may require.
Handler in, handler out
To do that, though, we need to write the necessary decorator functions first. For a simplest possible example,
let’s create one that automatically fills in the Server:
header of HTTP response with the name and version
of our web application:
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "HelloServer v0.0.1")
h(w, r)
}
}
Using it is then a simple matter of wrapping the original handler in a decorator call:
http.HandleFunc("/hello", WithServerHeader(hello))
As it both accepts and returns a http.HandlerFunc
— a function type matching request handlers’ signatures —
we don’t need anything else to be able to wrap our handlers in as many decorators as necessary:
http.HandleFunc("/", WithServerHeader(index))
http.HandleFunc("/home", WithServerHeader(LoginRequired(home)))
http.HandleFunc("/api/share", WithServerHeader(LoginRequired(CsrfProtected(api.Share))))
It’s possible simply by the means of ordinary function composition.
Going meta
Looking at the last line of the example above — which is made-up but could very well come from actual production code — you probably can’t help but notice that stacking function calls like that creates a somewhat messy amalgamation of parentheses. Beyond three or four items, a decorator chain such as this one becomes rather unwieldy to follow.
If we’re concerned about readability, it’s possible to alleviate the issue by taking another step up the abstraction ladder. Rather than composing the decorators explicitly, we can write a function that does it for us:
type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc
func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
for i := range decors {
d := decors[len(decors) - 1 - i] // iterate in reverse
h = d(h)
}
return h
}
and thus eliminate all the parentheses:
http.HandleFunc("/api/share", Handler(api.Share,
WithServerHeader, LoginRequired, CsrfProtected))
This technique proves especially valuable if the decorators themselves are parameterized:
http.HandleFunc("/api/notifications", Handler(api.Notifications,
WithServerHeader, LoginRequired, Cached(time.Duration(5)*time.Minute)))
In practice, you may find it valuable to wrap the complete http.HandleFunc
call to accept a request handler
along with its decorator chain, or the equivalent of such a call in any of the numerous
Go web frameworks.