and why am I using it?
elm (it is always a lowercase e, apologies, pedants and e e cummings haters) is "a delightful language for reliable webapps", according to the official website.
For me, elm is the answer to the following series of questions:
Yes, but it is hard. undefined
is not a function
, we hear.
It's hard enough that a great deal of effort has been expended creating build pipelines for javascript webapps to make things easier.
Wait, build pipelines?
Isn't javascript interpreted?
If we're going to preprocess our javascript, why don't we write in a language that compiles to javascript, but hates freedom less/kills fewer kittens, etc?
What would we want that language to look like?
Author's note: A bucket list of language features was removed from here. Suffice to say: elm is a good fit for me.
It's statically and strongly typed. Moving between types must always be explicit, there is no coercion.
It doesn't just prefer immutability - it requires it. Unless we start writing native modules, elm won't allow mutation at all.
How the hell do we write a webapp where anything changes, then? Well, elm isn't really just a language. It also provides an architecture (although I'd be more tempted to call it a runtime) and an excellent standard library.
elm grandly claims we can write a whole webapp with only four ingredients:
Model
, representing the 'state' of our app. This is usually an ADT, but it could just be a String
. We'll provide an initial value for this state, initialModel
.Msg
, that defines what events our application will produce and react to.update: Msg -> Model -> Model
. Given an event and the current state, produce the new state.view: Model -> Html Msg
. Given this state, produce a Html
view.You'll note that the return type of view
is parameterized by our event type. This means that our resulting Html
has hooks within it to trigger 'Msg' events based on user interactions, like a mouse click.
Under the covers, elm is running an event loop something like this:
var model = initialModel;
var currentView = view(initialModel);
applyToDom(currentView);
while (true) {
var event = events.poll();
model = update(event, model);
var newView = view(model);
applyDiffToDom(diff(currentView, newView));
currentView = newView;
}
I've represented that in terrible javascript pseudocode in the spirit of comprehensibility. I suspect that in reality there is not a queue, but a callback; this isn't going to be too important for this discussion though. Events turn up in the queue when events that trigger Msg
s occur.
We've used some functions I haven't defined: applyToDom
, diff
and applyDiffToDom
. The first of these takes the elm representation of our view and makes the DOM reflect it. The second, diff
takes two elm view representations and computes the differences between them. The third, applyDiffToDom
takes the resulting diff and applies it to the real DOM.
Whence events? Well, the Html return type returned from view has event sending attributes buried within it. This demo from the official documentation is the easiest to follow example.
Convinced? Good. Let's build something with elm.