Templates are Pure Functions
September 11th, 2007
It is often stated that it is a good idea to exclude code or functionality from templates. I have seen more than a few articles on the web entirely about how to approach this goal and the solutions are usually somewhat ad-hoc or arbitrarily restrictive. Some projects even seek to build a template language on top of the base language. We are typically left with the impression that there are no rules, or even good guidelines, by which to judge the amount of functionality performed by the template. Code seems like a necessary evil. We need it, but too much is bad and how much is a black art. Here is a simpler definition of a template: templates are pure functions.
A pure function obeys the following rules.- The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change as program execution proceeds, nor can it depend on any external input from I/O devices. This is known as referential transparency.
- Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
The singular purpose of a template is to transform it’s input into something viewable. If output does not depend entirely on input then the template is doing something it shouldn’t. This is boring and obvious to experienced functional programmers but, hey, we’re not all experienced functional programmers. Despite the theoretical simplicity there’s always a catch in the real world.
Automatic loading of composed objects muddies the pure function perspective. In this contrived example there is a user model composed of an address model with a street attribute e.g. user.address.street. The common practice, at least in rails, is to build a template which accepts the user model as input and chains to attributes of associated models. Does the output of a template using object chaining depend only on input? That depends on whether or not the requisite models were loaded before evaluating the template. If template evaluation caused a database hit then the output would partially depend on what was in the database at that time, breaking the pureness of the template. Automatic object loading is a hidden complexity that speeds development. Associations should be preloaded during production with a database join to prevent superfluous queries, a practice consistent with templates as pure functions.
There is no need to create a non Turing-complete subset of a language to build a proper template, nor is there a need to eliminate code from a template. The mantra that code doesn’t belong in templates is harmful and wrong. Templates may contain any code so long as the template remains a pure function.
Your Response