So, I’ve decided to use Elm as the gateway back into the browser world, and recently started to experiment with it. I’ve tried to push my understanding to its limit, to see if the hype was warranted.
A few characteristics of Elm:
- Statically typed, with union types being at its core
- Completely immutable
- Purely functional (there is a specific part of the standard library dealing with side effects)
- No non-sense tooling: the compiler is called elm-make, the repl elm-repl, etc.
In the Elm community there’s a standard way of setting up applications that emerged. It’s called, unsurprisingly, The Elm Architecture. I suggest you give it a read. But what is it all about?
- The application has three main elements: Model, View, and Update.
- Data flows uniderectionally: you setup events in the View, those will be processed by the Update, which will modify the Model. When the model changes, the views will be re-rendered.
This pattern, conjoined by fact that the model sits in a single place, allows for some spectacular tricks, my favourite being the time travelling debugger.
One of the things that I struggled the most while getting up to speed with Elm was its type system. The reason being, all functions can be automatically curried. Let’s see an example from the repl:
> hello name location = "Hello, " ++ name ++ ", from " ++ location <function> : String -> String -> String > hello "Alessandro" <function> : String -> String > hello "Alessandro" "London" "Hello, Alessandro, from London" : String
So, what’s going on here? I defined a function,
hello, that accepts two parameters, and returns a string. This emerges from the notation,
<function> : String -> String -> String. But what happens if we pass only one parameter of the two? The repl says,
<function> : String -> String, meaning we’re getting back the partially-applied version of hello, now accepting a single string, and returns a string.
As you can see, the compiler does an outstanding job in inferring types and gives you amazing hints when something is not quite right.
The type annotation for `myMethod` does not match its definition. - The type annotation is saying: String -> String -> String But I am inferring that the definition has this type: String -> String -> Int
While dealing with the types can sometimes be daunting, NoRedInk reported no runtime exceptions on their Elm code, since they deployed it in production. Not many companies dealing with front-end can match that sort of claim, I’m sure.
Also, the compiler is extremely fast; the feedback while developing is never a hinderance, as you instantly know what to fix.
The claim that mostly got my attention was this:
The Elm Architecture is a simple pattern for infinitely nestable components
Intrigued, I set out to create a Trello “clone”, Trellex, using Elm on the client and Phoenix on the server. The initial model was as follow:
I have a
Main module where the application gets initialised. The model is a
Board, which has many
List has many
The difficult bit is the fact that any event, wherever it’s been triggered, will go through the
update function you define when starting the application. Specifically, I have 5 layers to traverse back and forth when the event happens: after the event reached the
Main I had to ask each individual module how to update their respective sub-model. Setting up all the plumbing in place was very complicated.
In the end I changed the modelling completely, having all the sub-models directly available from
Main, and that actually made the application a lot easier to grok.
Perhaps it’s just me not understanding the correct way to deal with this. This could also be related to the language still to reach version 1.0: best practices and patterns have yet to be set in stone.
There is certainly lots of work I need to do to fully understand the language potential, but so far it’s been a great experience.