July 2008

Created 31st July, 2008 14:23 (UTC), last edited 31st July, 2008 14:31 (UTC)

I was writing about Django's template system trying to express something in particular about how you want a templating system to process text substitutions, but it all collapsed because Haskell is lazy and Python isn't. It was actually this distinction that I wanted to discuss, but I can't illustrate it properly using Haskell's type system and the only other type system that I know which can express this is C++'s, but those types look kind of ugly.

The lazy nature of Haskell and the immediate execution of C++'s model means that these two C++ types collapse into the same Haskell type of String:

std::string ( void )

The first is just the type of C++'s string, but the second is a nullary function that returns a string¹ [1I'm not going to write the boost::function wrapper type that you need to do anything useful with these function types in C++.]. Why does this matter? Well, the function that actually does the template rendering will have a Haskell signature of something like this:

render :: ( String, (String -> String) ) -> String

That is, it's a binary function that takes the template to render plus a context from which strings can be generated given a key.

context :: String -> String
    context "hello" = "there"
    render ("{{ hello }}", c) = c "hello" -- I'll leave a proper implementation as an exercise

In Python we would normally do something like this to provide a context to the template:

Template("{{ hello }}").render(Context({'hello':'there'}))

By the way, don't let the object oriented method invocation fool you into thinking this is substantially different from the Haskell function signature:

def render(t,c): return t.render(c)
render(Template('{{ hello }}'),Context({'hello':'there'}))

This method works fine for text substitution, but what if there is a complex text block that takes a lot of processing and is only sometimes used by the template? What we want to do is to introduce that lazy evaluation. For some reason this doesn't work though:

Template('{{ hello }}').render(Context({'hello':lambda:'there'}))

But I guess there's probably some other way of doing this as things like form objects can be dropped into the context and will be rendered. To re-express the context object in a more C++ way, the design you want out of a template system is:

std::map< std::string, std::string ( void ) >

Rather than:

std::map< std::string, std::string >

FOST.3's templating system is in many ways similar to Django's, but there are some differences. FOST.3's doesn't have any looping constructs. In this sense it is much more closely follows a declarative functional paradigm whereas Django's is much more imperative. As a consequence of this there are certain things that are simpler in FOST.3 and others that are harder – in an ideal world you would be able to pick the templating system that best suits what you want to do, and this is something that we are working towards.

Our next steps with the FOST.3 template system will revolve around making the virtual machine that the templates are executed within much more explicitly a programming language environment. We have what we think are some good ideas for ways to do this which we want to experiment more with and see how they turn out.

Where we are heading towards is a syntax-less programming language where we will only define an execution model (which will be as loose as we can possibly make it — declarative and functional helps a lot there — as we want to be able to parallelise it as much as we can) and an abstract syntax tree, but with the added twist that ability to call any given function in the language will depend on a security check — the code will do substantially different things depending on who is running it. I'm looking forwards to experimenting with it!

Now off to write some more of it…