A Lazy Sequence

Templates are functions

Over the last decade or so of web development, templates have developed from a side show to a major pillar of developer religion. No matter what your web-stack faith, some form of templating ideology exists. Despite the wide range of belief there is a conceptual kernel they all share: A template is a function of some context that returns an HTML document.

Map<String, Object> → HTMLDoc  

These functions are typically bound by convention (and often implementation language restriction) to avoid side effecting operations such as database queries or updates. This restriction makes templates an instance of pure functions.

Historical and organisational concerns have caused templating systems to develop common traits:

The rest of this article looks at those traits in the context of pure functions.

The cottage industry of producing new templating systems has tended to be an iterative process, slowly refining the concept. One commonality is that the lion's share of the developers of these systems have learnt from the mistakes of PHP and want to avoid the spaghetti nightmare of business code and template being intermingled. The other commonality is that these languages have arisen in the context of the dominant paradigm of the industry: procedural and object oriented code. The result is a greenspunning of functional-esq declarative languages.

It is my opinion that if templating systems for procedural languagesi were built around defining templates as pure functions, templating would have greater reuse, and allow more expressive, declarative templatesii without sacrificing reasonabilityiii.

Returning to pure functions, let's examine what they provide:

These two properties allow us to describe chunks of our template and reuse them without fear: no side effects will occur, no violation of our system's areas of responsibility will take place, and we can always reason about the templates.

What about declarative templates? The canonical example in functional programming is map and filter:

function inc (x) {
    return x + 1;
}

function is_odd (x) {
    return x % 2 == 1;
}

map(inc, [1, 2, 3]); //=> [2, 3, 4]

filter(is_odd, [1, 2, 3]) //=> [1, 3]

map(inc, filter(is_odd, [1, 2, 3])) //=> [2, 4]

The important aspect here is that functions are passed to other functions, allowing the mechanics of how an array is processed to be internal to map and filter, and the caller only describe what to do.

To extend this to templating, we would be passing templates to other templates/functions. For example, the Jinja2 templating languageiv for Python has a macro tag that allows you to define first class templates in your template file. These first class templates output whatever their body evaluates to. For example, map above could be written as:

from jinja2 import Template
t = Template("""
{% macro inc(x) %}
    {{ x + 1}}
{% endmacro %}

{% macro map(t, l) %} 
    {% for i in l %}
        {{ t(i) }} 
    {% endfor %}
{% endmacro %}

{{ map(inc, [1, 2, 3]) }}
""")

print t.render()
# 2 
# 3 
# 4

In this example, map and inc are both first class templates, and map is paramaterized with inc. While the example trivialises this, in practise it can be very powerful. This kind of paramaterization allows you to dramatically reduce repeated code.

However, because these macros output their body, the effective return type is always approximately string. As a result, we cannot create a pipeline of map and filter as seen in the javascript example above. This implies that while first class templates are a powerful addition, we are still operating in a space that is more restricted than full pure functions.

What would you gain by allowing any pure function be defined within a template? Template authors could trivially create declarative operations to support the domain of their site, rather than either

A simple hypothetical here would be taking a set of common filtering operations and a composition function to create a single filter that is then used throughout the codebase. This might look something like:

{% define summarize=compose(safe, trim_words_html(30)) %}

…

{{ post.body|summarize }}

The argument against this is “Simplicity”. However, this argument favours a surface level simplicity that is anti-abstraction. Being able to define domain specific abstractions is the cornerstone of software development; why are we excluding this from templating!

Templating systems in an Procedural / OO environment are a functional microcosm. Pure functions should be the basis for any templating system; all other concerns are a side-show and in general fall out of using the functional model. Starting with a well understood and coherent model allows greater reuse.

Any time a templating system makes a decision with its templates that is less expressive than composition and application of pure functions, I believe it is worth evaluating whether that trade off is worth it. Everyone has different requirements, but I want templates that encourage abstraction, reuse, and composition over artificial simplicity and feeding the sacred cows.

  1. Users of functional languages should consider if they really need templates to look like those of procedural languages: If you already have pure functions, why reinvent them. If you are familiar with Clojure, you will know that the two most popular templating systems – Hiccup and Enlive – are dramatically different to what you might find in (for example) Python.
  2. Anecdotal observation: declarative APIs in OO often allow use a predetermined set of declarations to describe your problem, but its not uncommon to encounter difficulty creating new declarations. In functional programming (a great example is parser combinators) you can define new declarations easily, often as a composition of existing declarations.
  3. I think of reasonability as an informally term meaning that the code is easy to reason about. You don’t need to execute the code (with or without a debugger) to understand it.
  4. If you use Django, and have not yet explored Jinja2 as an alternative to Django’s templates, I would urge you to do so. In my opinion it is a great improvement over Django’s defaults, while maintaining a consistent flavour.
21 October 2012