I’m still on the hunt for “the right” programming language for web front-ends. JavaScript is fun and very good for quickly hacking together something, but as soon as your project grows you either need a large number of tests and discipline or your going to break something with every refactoring. TypeScript seemed like a good rescue - but coming from Haskell I have high standards for type systems and the TypeScript one still has loop holes. The other problem with both languages is, that you are responsible for managing and syncing your state and model correctly. React and otherframeworks help you with this, but you still have to use them correctly and there’s always a way to sneak around. Elm to the rescue? Let’s see!
Warning: the recent Elm Version does things a bit differently, so this walk-through will not work anymore
Diving into Elm
If you’re not new to Elm, jump to section ‘Real world Elm’
The update part defines a pure function running an action on your model
The view
The view is just a pure function converting your model into something “renderable” like HTML or a canvas image.
It’s really just that simple. And it comes with some cool advantages: Your update function is pure! This means you can test is very easily. There’s a simple package to wire all this together called start-app.
Wiring it all up
That’s all that’s needed for a tiny calculator. I left out the “boring” parts like imports and the full update implementation, but the full code for this calculator is available.
Coming from Haskell implementing this was pretty quick and fun. The only major issue I came across was that it’s quite hard to explore the Elm ecosystem. The types in the documentation are not yet hyperlinked, so it’s difficult to trace down what comes from where and how everything should work together.
Real world Elm
The next logical step for me was to try out Elm in the real world. The frontend of TramCloud currently heavily relies on React and JavaScript; but it’s also very modular so I decided to implement a new component using Elm. I first created a new module to access the backend API:
The Json.Decoder needs to match our Haskell aeson instance for the type, and I don’t think writing all this by hand is a good idea. But I’ve already got something planned to automatically generate an Elm API module from Haskell using Spock and highjson. Stay tuned for that ;-)
Now we need to write the actual logic working with these ReferenceProfiles. We would like to define a table to display them and allow actions like modifying and deleting. This means jumping through the same steps as before: model, view and update:
The model
The model is just the list of ReferenceProfiles:
The update
The update part is a little bit more complex, as we want optimistic UI. Let’s define our actions first:
The ServerQuery are the possible queries that can be sent to the server. The ServerMessage are the possible responses. The Actions on the model are a combination of the queries and the responses. This is important, as that’s used to implement optimistic UI.
Now wee need several Mailboxes to manage queries and responses:
You can think of a port as task runner for Tasks that need to interface with the outside world. Note the default SqRefreshProfiles in filterMap to load everything on app launch once. Now we are ready to define our update function:
Nothing surprising here, just applying the actions to our model.
The view
Just rendering the Model to Html and wiring our Address to our button(s).
Wire it up
Now we need to connect everything:
Signals coming from the UI and the Server should go into our update function and fold over our Model
Signals coming from the UI should trigger custom logic that may send HTTP requests (serverQuery mailbox)
The UI should not be able to send an empty (Nothing) signal.
That’s it! Optimistic UI, talking to a Rest API, a good looking UI and maintainable code. All this took a day to figure out and once it typechecked it worked. Cool! I can not draw any final conclusion on Elm yet (have not used it enough), but it looks very promising. I will continue to use it for now.