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:

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.