Here, I'll focus on the historical perspective that makes Elm worth a look, and will go deeper on the language's two main features: functional purity and static typing. After that, I will point out some good examples about who is using Elm in production.
The low-profile success of Elm's architecture
The best way to predict the future is to invent it.
Alan Kay, designer of the Smalltalk programming language
The one-way data flow
React was the first project shaking this imperative house of cards. The most distinctive feature of this project is the virtual DOM, which enables programmers to think of their web page as something that can be efficiently re-calculated and re-rendered without big costs.
Such efficiency is what enables React's way to model the front-end: A webpage consists on a tree of components and each time the user interacts with them the whole page appearance is re-calculated based on the new state stored within these components. You do not have to actively change the DOM anymore; React takes care of that for you.
Despite the fact that React made it easier to model web applications, it did not prescribe a way to separate the business logic from the rendering logic. Facebook did propose an architecture called Flux to solve this problem, though many of the former implementations were more complex than needed and were taking time to gain traction.
A functional architecture for React applications
It was in 2015 that Redux appeared and emerged as a simpler implementation of the Flux architecture. The main idea is that the application's state should be stored in a single state tree and that changes to this tree should be modeled as a reducer function. This should itself be the result of the composition of several reducer functions, each being responsible for part of the state of the application, modeling the logic to process each action and obtain a new version of the state.
Redux is a simple library with a very small core (around a dozen of small functions). It is easy to grasp, pleasant to use, and allows the use of cool development features like time-traveling debuggers and hot module reloading.
Courage is knowing what not to fear.
When a software project starts, all the code is beautiful, pink and shiny. Then, you start to face deadlines, bugs, hotfixes, scope changes, company merges, acquisitions and a horde of over-allocated developers changing the code while the original author is on vacation, sick or dead. After aging, every line of code carries the height of its history and often nobody is there to tell.
In praise of static typing
Statically typed languages - on the other hand - check the semantics of the project and assure its correctness. The names might be wrong, but we know which operations match which types and the compiler automatically checks if our changes make sense.
The information provided to the developers by such systems is worth a thousand names and those systems are easier to change and require much less testing because the type system avoids several errors that would otherwise have to be checked for by an automated test developed by a human.
The downside of static-typing
The problem with types is that - in most languages - they get in the way when developing new code and debugging or experimenting how the code behaves. Usually, types add a lot of unnecessary noise to the clear speech that developers have with
the machine other developers while coding.
With types, developers are also forced to write more - and while that might not be very bad when using the editor's auto-complete - it is a catastrophe when experimenting in interactive environments like the command-line or the debugger. The bulkiness of having the code full of type information creates plenty of inertia and implies a huge loss of agility. Choosing between static and dynamic typing thus ends up as a tradeoff between Usability and Maintainability.
However, there is a common trend amongst all these approaches: In order to avoid the static typed bulkiness they have chosen to make types optional. Thus - depending on the context - the developers are free to either add type information to their code or to choose working without type checking - and the type safety that comes with it.
The results are better than without types, but still relatively limited. As types are gradual, it is often impossible to check if the type signatures make sense, and the amount of verification depends heavily on the developer's discipline and on whether the libraries used have type information for the same type system.
Elm has a different approach
Elm's ideas come from the typed functional world, in which decades of development resulted in the use of type inference. Unlike with dynamic or gradual typing, the compiler checks the types on the code. Though, unlike most static type systems, the type information is not required to be explicitly entered by the programmer and is inferred by the compiler.
The programmer may choose to later add type signatures to his functions, though these type signatures go separate from the function definitions. This way, they do not become confusing to whoever later reads or edits the code.
Pure functional programming
True wisdom comes to each of us when we realize how little we understand about [...] the world around us.
Elm is a purely functional programming language. This means that all its expressions enjoy a simple property called Referential Transparency from which stem several interesting consequences. We proceed by describing what is Referential Transparency and then explore its consequences.
An expression or function is said to be referential transparent when its results behave in a logical manner. In other words, when provided with a certain input, a referential transparent expression will always evaluate to the same result.
Pure functions are easy to reason about and to test
If we think of the mathematical concept of function we may notice that it indeed enjoys this property. It is actually quite hard to imagine how could it be otherwise. The function that calculates the area of a square always evaluates to 4 square meters when provided with a 2-meters input and will always behave as such. There is no way to write a function that will take the side of the square as single input and have it return different values at different moments in time.
At least in the mathematics that most of us have been thought, mathematicians seem to have chosen such abstraction as a very important - if not the main - tool to model and reason about the world. I would say this was no accident.
If a function always returns the same results, we can evaluate and test it without having anything in account except for its inputs and outputs. We know exactly what to provide and what to expect when thinking about modeling and testing it.
But referential transparency is not a reality in most programming languages. With the development of digital computers, most programming languages chose to model the world in a way akin to the way the machines internally work rather than to the one mathematicians used to model the world.
There were attempts to follow the first path since the development of the first compilers. However, the computers at that time were not powerful enough to handle such computational model and the machine-like way to model the world became an ingrained part of our computer-science culture.
That machine-like way to model the world (imperative programming) is mostly based on a mutating state. In it, computations can be modeled by reading and changing the state of a memory unit until a final state is computed. When adding functions to this model, most languages chose to share this state amongst functions thus breaking referential transparency.
Functions do not depend on their input; their outcomes depend on the memory that they read and write, and they often do not return any value, solely serving the purpose of changing the values in the shared state.
In contrast, in purely functional programming, no state is shared amongst our functions. And if no state is shared, there ceases to be a reason to change the state of the values that are being used. The aim of our functions stops being about changing values and starts using them as inputs to calculate other values. In purely functional programming, every definition is immutable.
Elm fits on this language family and, thus, all its definitions are immutable and all its functions are referentially transparent.
The runtime is in charge of side-effects
Pure functions alone are useless. We perform computations because we want to read input values from the outer world and because we want to change it according to the values of our computations. The outer world is thus like a state that is altered by our program in the same sense as the memory of our imperative programs.
Some functional programming languages found mathematical ways to deal with this issue elegantly in pure-functional style and in a general purpose way, but by using some concepts that have shown being quite hard to grasp.
Elm has a different approach. Its runtime and architecture hides this problem from us. Our program ends up being a set of functions used to calculate what to display to the user given a certain sequence of user actions or other environmental inputs.
Basically, the Elm runtime and architecture take care of interacting with the world in this particular domain, making it in a pleasant and simple way.
The future is not laid out on a track. It is something that we can decide, and to the extent that we do not violate any known laws of the universe, we can probably make it work the way that we want to.
Elm's ecosystem is quite immature and is evolving slowly. However, the library's quality is often very good and the compiler gives us plenty of guarantees about their stability. For now, Elm is appropriate for small dynamic webpage components or for simple webpages that do not require server-side rendering.
There are some important features only expected to come with the following version (0.19):
- Server-side rendering - sending HTML with the initial response;
- Tree shaking - trimming out unused code (usually called dead-code elimination or DCE);
- Code splitting - cutting up code into smaller chunks for better caching;
- Lazy loading - only sending the code chunks needed for a particular page.
The main objective of this version will be to provide an acceptable system to develop complex single-page web applications.
Dressing up is inevitably a substitute for good ideas. It is no coincidence that technically inept business types are known as 'suits'.
Despite its immaturity, there are already some success stories in the industry:
NoRedInk is a company in the educational-software field;
Pivotal Tracker is a famous company that provides tools for agile development. They have recently started using Elm and seem to be having a very nice experience with it;
Prezi develops software to create interactive presentations in the browser;
CBANC Network - A network of banking professionals with tools for banks to collaborate as well as manage their business. All new front-end development is being done in Elm;
CircuitHub (GitHub) - CircuitHub provide on demand electronics manufacturing with instant quotes;
YariLabs - A Portuguese company developing software using functional languages;
Other companies: Adrima; Bendyworks; Futurice; Gizra; Test Double; Mimo; SMRxT; TruQu; PinMeTo; Hearken; Day One; Spottt; imby.bio; wonktonk; Beautiful Destinations; AS Tallink Grupp; CARFAX.
Context is worth 80 IQ Points.
Elm is heavily inspired in Haskell and heavily inspires Purescript. Here goes an outline of what these are all about:
Haskell is by far the language that most influenced Elm. It is the current standard for lazy typed programming languages and is developed by a committee of academics from the field. The main differences from Elm are:
- It is a general purpose programming language;
- It provides a very polymorphic and complex type system;
- The evaluation is lazy (expressions are only evaluated when their values are needed);
- Usually it is compiled to assembly;
- Has runtime errors.
Purescript falls somewhere in between Haskell and Elm. Both are evolving together and influencing each other as they grow. It's main characteristics are:
- General purpose programming language;
- Like Haskell, supports a very polymorphic and complex type system;
- Worse error messages than Elm;
- Like Elm (and unlike Haskell), it supports strict evaluation;
- Has runtime errors.
There are three projects in this language's ecosystem that try to address the same problem as the Elm architecture:
- Thermite - A wrapper around React;
- Halogen - A front-end framework taking advantage of the Purescript's complex type system;
- Pux - When using Halogen they realized that the elaborate typing made it very hard for beginners, they then implemented Elm's architecture in Purescript, without deviating much from the simplicity of Elm's approach. The outcome was a framework called Pux.
Elm is an interesting front-end specific programming environment that supports an immature software ecosystem. Due to the language design this young ecosystem ends up providing atypically strong safety guarantees. Many of these stem from the friendly compiler that guarantees that there are no runtime errors.
For now, it's not ready to develop complex single-page web applications due to lacking server-side rendering and some important optimizations. The next release (0.19) is expected to address these issues and while it is not ready it might not be a good idea to use it for more than small applications or components.
It is worth keeping some attention on this project that seems to have the potential of becoming a very competitive tool.
At Imaginary Cloud we have a team of experts on software development. If you think you could use some help with your digital project, drop us a line here!
Found this article useful? You might like these ones too!