I'll discuss how you can build things in Elm in a follow-up blog post, but in this post I'll focus on my preference for Elm. As any other developer working on a frontend knows, building a great maintainable application for the web comes with its own set of challenges, it's true.
When I mention functional programming constructs like immutability or pure functions my focus will be on what they offer and not how they work. There are experts in functional programming who are better at explaining these things. For starters, check out this adequate guide.
Simplicity and discipline
In our line of work we're constantly performing a balancing act between creating the perfect technical masterpiece and cost-time effectively adding value to a product. Making a choice between doing what's 'best' and what's 'the convenient thing'. Following best practices in an attempt to reign in complexity and keeping your codebase maintainable often takes more time and of course knowledge of what these practices are. Knowledge that needs to be spread throughout your team and something you and your peers will 'live' by.
Taking angular.js (1.x) as an example. Looking at the styleguide written by John Papa, there's a plethora of reasons to do the things he does. Mainly because there's a lot of things you can do to hurt yourself, and not a lot of tools to fallback on like a compiler or a type system. The popular choices for frontend projects like Angular2 and React are similar, even though new tools like TypeScript, flow.js and Elm inspired libraries like Redux take some of the pain away. The crux is: you need to use them and make sure your peers do as well. The pitfalls are still there and avoiding them takes discipline. Automated tools like code linters offer insight in the basic do's and dont's but don't really have an opinion on your code.
Speaking from personal experience I know that growing the needed discipline and knowledge takes time and effort. Time and effort you could have used to create more value for your customer. This is precisely why you need the same discipline in communicating with your customer, as they need to understand good work takes time. I'm not saying that isn't the case with Elm but a lot of pitfalls simply don't exist in its realm, because of the way it is designed.
In addition to the pitfalls, a developer has to take the time to orchestrate and tweak all the libraries and frameworks to work in unison. For example, getting TypeScript to play well with Immutable.js introduces more moving parts inside your code.
Elm is opinionated
The above combination of Immutable.js and TypeScript is based on the assumption that developers want more help from their compiler and the predictability of immutable data. Because TypeScript has been steadily gaining popularity and more is being developed in a functional way, my guess is that this is correct.
So my view on the current state of frontend is that the most popular frameworks and libraries are introducing stronger typing, better management of state and a general lean towards a more functional approach of front-end. In a talk at ReactConf 2016 Richard Feldman states that React was the first library to coin the idea of your view just being a function that produces HTML/UI. Angular2 also moves away from the 'traditional' MVC-pattern and follows into the more component-based approach of React. Both of these popular flavors are often used in combination with a way to update your state through messages like Redux and NgRx/Store.
The things you see almost every other modern 'camp' gravitate towards are built into Elm from the get-go. Elm is designed to stay out of the developers way and be something you can really trust to deliver.
Immutability, pure functions and state management
As humans we have a limit to what we can actively keep in mind and work on. Keeping that number low is what immutability, pure functions and a single state object managed through dispatched messages do for you. Again my point here is not that it can't be done in other frameworks or languages but that these things are first class citizens in Elm. So what benefits do these concepts have?
Most of us probably know the definition of immutability, however here is a little refresher:
In object-oriented and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created.
And why is this useful? Immutable objects are inherently thread-safe, are simpler to understand and reason about and, not only that. They offer higher security than mutable objects.
Pure functions and their use
You can rely on the fact that these functions always return the same result given the same input because they cannot rely on state outside their scope. This means side-effects are not available within your Elm code. So when a function is evaluated, nothing outside that function will be changed. It purely returns the data it evaluated to. All functions in Elm are referentially transparent, which has multiple benefits. Most importantly, it allows you to reason about these functions in isolation and depend on their results.
The Elm Architecture or TEA
When building a frontend application you need to respond to user interaction and provide the user with the next available view or set of actions he or she can perform. The Elm Architecture is a pattern that provides you with a way to do this.
- State is kept in one spot, this way you have only one source of truth and this eliminates ambiguity. You always know what data your program relies on.
- State can only be updated through messages sent by the Elm Runtime (see image below). These messages come from the user actions you bind to your Elm HTML functions. Because messages are the only way state is updated you can always retrace your steps and your state becomes reliable and retraceable.
- Your view is simply a set of functions that map the state object to HTML, and because there are no side-effects you can more easily determine why some HTML is rendered in any given situation
A nice feature or the message based updating and single state object is the ability to use a time traveling debugger and import/export this state to easily reproduce bugs.
The friendly compiler and its type system
However, to increase readability and provide the compiler with more information you can choose to use Type Annotations. They look something like this `retrieveByAge: Int -> List Person -> Person`, which means the corresponding function takes an integer and a list of Person records and returns the Person with the right age. In my experience I find the annotations fill the role of documentation well. They even allow me to design my program by just typing out the annotations first.
Another important feature of Elm is that there is no `Null`. To deal with missing values Elm provides `Maybe`, this is a type that can be either `Just` the value or `Nothing`. Within your program you can recognize this and are forced to handle both situations properly.
All of this helps to mitigate runtime errors, eliminating errors like `TypeError: undefined is not a function`, unless you're really trying and write infinitely recursive functions that will produce errors on runtime.
Testing is king
More often than not ensuring your code is testable and writing tests takes up a relatively large portion of your time. It can be daunting to set up Selenium for end-to-end testing, or to set up Karma for unit tests with the right setup to for example satisfy the dependency injection of Angular2. Elm provides some advantages here. You won't have to write as many unit tests since Elm has pure functions (one unit test per function per input) and a lot of usually typical unit tests become superfluous due to Elm's strict compiler (no null value unit tests). Elm even allows property based testing.
What I personally find very interesting is that using `elm-reactor / elm-make—debug-mode`, non-devs can test and in case of a bug export state which you can import and replay. Reproducing a bug becomes very easy because of this. It's a real time-saver.
Sounds great, doesn't it? An often heard argument is that Elm is only at 0.18 and that doesn't inspire enough confidence to use it in production. However there are companies that already made the plunge and are running Elm in production environments successfully:
- Avisi a Dutch company based in Arnhem, my home town, is using Elm in production in Atlassian products they work on.
- NoRedInk, the homebase of Evan Czaplicki (our benevolent dictator), running 80,000 lines of Elm code in production. Introduced Elm in 2015 and the Elm code has thrown a total of zero runtime exceptions.
- Prezi and some other companies have their logo's on the elm-lang.org homepage.
- Interesting enough TruQu is yet another Dutch company using Elm.
Take a look at some more cool stuff
Elm and it's ecosystem offer more than just the things I described above, a small selection:
- Elm CSS, a pre-processor for your css. Since it's Elm, the same guarantees the compiler brings go for your styling. It can be tested and your css classes and Id's are part of Elm types.
- Elm Analyse helps you analyse your code and educates your on idiomatic Elm.
- Elm is blazing fast.
- It's possible to use Elm and webcomponents to wrap and reuse jQuery/JS plugins, you're not limited to just Elm. It knows how to play ball and not lose the guarantees Elm offers.
- Package system with enforced semantic versioning, compared to NPM very powerful. Through the typesystem Elm can detect API changes. So no surprises in patch releases.
To Elm or not to Elm
In my experience Elm takes some getting used to because of the types and declarative style of programming. However, precisely because of the concise syntax it quickly turns into a pleasure to read. Moreover, it provides developers with a lot of safety.
So here's my advice: try it on either ellie-app or elm-lang/try. Build a tiny bit of functionality in your program with Elm. Evaluate and probably try again! Go to meetups. Join the community. Perhaps read one of my upcoming blog posts. And if you have any questions about Elm or this blog, feel free to send me a tweet or message on the elm-slack channel.