If you need a refresher on what functional programming is check out my previous blog post.
JS is the only* language standard that is supported by all major browsers and allows for interactive apps. If you want something except from a static HTML you need to use JS. To write an app in another language you need to compile it to pure JS, like many web languages do. Most of them, like TypeScript, are based on JS and offer limited improvement over JS problems. Elm is something entirely different, having no trace of JS's imperative syntax and error prone type coercion, but still compiles to JS in the end to be run by the browser.
*Web assembly is starting to gain popularity, but it's still in developement
By being a pure functional language Elm can guarantee that no matter the time of day, the randomness or the user input, the app will always be ready for whatever comes and will process it without breaking. That doesn't mean any input is valid, just that invalid input will produce error values, which must be explicitly handled in the code or else it won't compile.
Let's take string to number conversion for example. In JS there are multiple ways of doing it:
+x among others. All of them return a value of type "number", but they can actually output what is literally Not a Number, and in case of
+x they may even just silently ignore invalid strings and return
0. This may lead to some problems in later stages of the program which will be hard to debug because of those silent errors. Solutions to this include a separate check if the string is numeric, which arguably should be a part of the parsing function, and checking for
NaNs every time you handle a "number", which should not be necessary because
NaN is Not a Number.
In contrast, Elm's
String.toInt function returns a
Maybe Int type, which can have values
Just Int. This is an explicit sign: "Something can go wrong here and requires handling!". This value can be then passed along as
Maybe Int, or it can be branched on and some handling can be done in case of
Nothing, and in case of
Just Int just the
Int portion can be returned. In fact this pattern is so common that there already are functions for handling
Maybe values, the most obvious one being
withDefault which takes a default value and substitutes it in case of
Nothing. This is again explicit and allows more control over the behaviour of the process instead of silently passing erroneous values and creating an undebuggable mess.
Being a pure functional language Elm can't have functions that rely on data outside their inputs or change data outside their outputs. This means working with time, randomness, user input and DOM shouldn't be possible to implement. Elm solves this problem in a very elegant way: you don't write the entire application. It has a runtime system, and your job as a programmer is to provide this system with three things: the initial application state represented in Elm values, a function from state to DOM that will be displayed to the user, and another function from the state and messages that the user may send to a new, updated state. The runtime then renders the page using the initial state and the view function and handles any user interaction by sending appropriate messages that you defined to the update function. All impurity is abstracted to the runtime, so the user can stay in the pure language of Elm.
There are multiple benefits from this. First of all getting to stay pure guarantees no runtime errors. All the input data comes as messages that you defined and are forced to handle in the update function. Pure functions also benefit heavily from compiler optimisation and caching results: even though the view function is called after every update the code is compiled to only calculate parts of the page that changed which keeps the system efficient.
The Elm architecture also serves as a good design pattern that makes thinking about your code easier. It makes you make a model - a representation of the application state in terms of in-language values - and consider what states your app can be in and what can change about it. It forces you to consider all transitions of this state: what to show the user when the additional content loads? How to react when it fails to load? All this is turned into messages that have to be handled or the code doesn't compile. According to the Elm website many JS frameworks and other application languages have adopted the Elm architecture, more or less openly. It's a design pattern that any language can benefit from.