Thursday, February 4, 2016

ftmpl: a fast and typesafe golang (html) templating library

I always wondered why so many templating "languages" expect you learn a completely new language. For example, jstl, django templating, and golang templating. I know what a mess PHP can become if you mix too much PHP code, html and javascript code. But I, also, think that most developers today know that this is a no-no.

Another thing I dislike about templating libraries is that so many of them are type unsafe. Even when the original language is typesafe.

For example, in Golang:
type Inventory struct {
 Material string
 Count    uint
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }
But what if you call:
err = tmpl.Execute(os.Stdout, "jkljkl")
The template expects an argument that has Material and Count fields. But I executed it with a string! This will fail, but -- not in compile time! Keep in mind that in most cases templates are created in advance (not in runtime). This error should be caught in compile time, if you ask me. Especially if you (like me) moved to Golang from a scripting language -- because of its type safety.

Fast forward to my ftmpl... Ftmpl is a fast, typesafe templating for Golang I created for my last side project (more soon). Ftmpl generates Golang code from .tmpl files. Every template is "converted" to a Golang function, then you just call that function. If you declare your template with an argument Inventory then you can't call it with a string. The error will be caught on compile-time not in runtime!

See the README for more niceties (like extending templates, autoescaping, formatting output, ...).

My experience with golang

I rewrote a big part of Trackprofiler, my online GPS track editor. The web application is still mostly Python, the main difference is that all the GPS tracks manipulations (GPX files) are done in a microservice written in Golang. The main reason is that Python proved to be too slow and memory hungry for big GPX files.

I'll list a few things I learned in the process of rewriting it to Golang:

First, I want to emphasise that learning Golang has a lot less "WTF moments" than learning other languges.

My project has cca 9000 lines of code. I think that equivalent Python code is shorter, but if you write unit tests (you should), a lot of the Python ones will test things which in Golang aren't needed (because because they are caught by the compiler!).

The golang code tend to have more (but shorter) lines because in Golang you don't write things like:
Any of the .DoSomething(), DoSomethingElse(), ... calls can "throw" an exception. In many languages you just expect that somebody else will catch those exceptions. In Golang there is no "throw an exception". If there is an error you need to handle it immediately. The result is that you write every operation in a new line, and not cram them all in one.

But that's not necessarily bad, you have more lines, but they are all simple operations and easier to read and understand.

I thought that a big problem would be no DOM-based XML parser, but I love the builtin JSON and XML APIs!

Versioning dependencies sucks.

There are a lot of libraries*, but most are not mature enough. This will probably change with time.

And, yeah, it has no generics.

*If you are interested, here are my own open source libraries and utilities:

typescriptify golang structs

I'm a big fan of Go(lang).

I know, I know, Go don't have generics. I miss them, too. But on the other side -- it's simple, it's fast, vim go is great. Writing code is straightforward, it's readable. Golang is more like a better C (with a more consistent API and a garbage collection) with a feeling of a scripting language. It is not a "better Java".

Another language I (started to) like is Typescript. Not because I especially like the language or its syntax. It's because it is:
  • typesafe,
  • compiled to Javascript and the resulting code is similar (almost line-to-line) to the original Typescript code (making debugging very easy).
In my latest side project I decided to go the Go+Typescript way. There was only one thing missing. It is a web application with a lot of Javascript (i.e. Typescript code), but the data used in Typescript was not typesafe. On the server, the data is represented with Golang structs, but Typescript receives only their JSON representation. 

I solved the problem by writing a library which converts Golang structs to Typescript classes:

It's simple, you just:
tscriptify -package=package/with/your/models -target=target_ts_file.ts Model1 Model2
....and then include target_ts_file.ts in your TypeScript scripts.

Make this step part of your bild process and... Now, if you change a field name in Golang -- the javascript/TypeScript compile will fail!