Elm is a framework and typed functional programming language aimed at front-end development. It inspires itself in a programming language called Haskell to bring more maintainability to Javascript, though keeping itself as simple and close to Javascript as possible. The architecture of the framework is similar to Redux, that actually was heavily inspired in Elm.
Forget what you have heard about functional programming. Fancy words, weird ideas, bad tooling.
— Evan Czaplicki, Elm's Creator
When designing the language, Elm's author has chosen to put the user at the center of his design choices. If you take a look at his "Let's be mainstream", you'll notice a strong focus on who is the user (The Javascript programmer) and what does he need, by employing the so-called Usage-driven design. This philosophy reduces the language features to the essential and makes it pretty easy to be learned.
[A] recently hired software designer had spread out source listings on the conference table, and carefully passed a crystal hanging from a long chain over the source code. Every so often, the designer marked a circle in red on the listing. Later, one of my colleagues asked the designer what (s)he had been doing in the conference room. The nonchalant reply: "Finding the bugs in my program".
This is a true story, it happened in the mid-1980s when people had high hopes for hidden powers in crystals.
— MIT Press, Introduction to Algorithms, Eighteen printing (1997)
Elm's error messages are extremely friendly. On one hand, this comes from the static type system, that allows the compiler to clearly know and state the differences between the types of the values that are expected and the ones that are being provided to our functions. On the other, as there are no type classes, the type of information shown in error messages is very down-to-earth:
In addition to this atypical clarity, the language designer decided that error messages should be a priority, and thus, in addition to formally state what is wrong with the programs, the compiler also provides 'hints' about what might have been the programmer's intentions and provides suggestions of possible fixes, for instance:
[Haskell is useless.]
— Simon Peyton Jones, Major contributor to the design of the Haskell programming language.
That's right, this is no Haskell - Elm's main source of inspiration. While following the usage-driven design process many of Haskell's features were not needed and thus not added to the language. Amongst those was the infamous type classes system.
That system would bring with it all the pretentious-sounding mathematical vocabulary typically heard amongst Haskell programmers and render Elm totally inappropriate for the "regular Joe".
But don't we need type classes? They must serve any purpose! Why does Haskell need them and Elm doesn't? - Well, the answer relies on the problem domain addressed by each of these languages: While Haskell Is a general-purpose programming language, Elm is not. On one hand, Elm is simply a frontend programming language and is able to accomplish that task well without resorting to so much complexity. On the other, part of what such complexity aims to deal with is handled by and hidden in the Elm runtime - which takes care of handling side-effects and mutation in a domain-specific fashion.
We’ve had zero run-time failures [...]. We’ve also had fewer bugs, largely because Elm’s type system forces you to model your domain more carefully. [...] To sum it up, our manager has mandated that all new code be written in Elm.
— Jeff Schomay, Front end specialist at Pivotal Tracker
Elm's type system is very safe and there are two consequences of its design that are worth notice:
My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation [...]
— Tony Hoare, Inventor of the null reference
While other programming languages handle errors as a part of the language with very specific features - usually called Exceptions or Errors - Elm does not feature any specific primitives to trigger errors or to handle them. The possibility of failure is rather modeled trough the types of values that are returned and handled by functions. Some parametric types may be used to convey information about failure, such as the types Maybe
; Result
and Task
.
The Maybe
type is the most simple of these. It is defined as:
The a
in this expression might be replaced by any specific type, e.g. a value of type Maybe String
may either assume a value like Just "some specific string"
or Nothing
.
Thus a function returning a value of - for instance - Maybe String
can return a value Just "I am the returned string"
when succeeding or return Nothing
when failing. The programmer is later always forced to choose what to do in both cases and the language does not allow the program to compile if both possibilities are not handled.
It is through the previously shown approach that the language avoids having a null value. And thus get rid of all those usual undefined is not a function
errors. If there might be a failure then the type will explicitly convey this information and the compiler will force this case to be handled by the programmer.
As Elm is a pure functional language, the purpose of evaluating something is to get a value from it. Thus, Elm forces the developer to provide a return value for every control statement. This implies that every if
statement needs an else
clause and case
statements must handle every possible input value.
For example, let's take a look at an abstract data type TypingDiscipline
that might assume as value Static
; Dynamic
or Gradual
:
and if we're handling some value of this type as the input of a case statement (a switch statement), but forget to handle one of the possibilities:
the compiler does compile the program:
Thus when changing our types to add more functionality, we end up being notified by the compiler that there is some code needing to be added in order to deal with a new possibility.
You’ve got to find what you love.
— Steve Jobs
Elm's short feedback loop and uncluttered syntax make it a very pleasant environment to work with. Let's take a look at how these things contribute to the Elm's developer joy.
Avoid success at all costs.
— Simon Peyton Jones, Major contributor to the design of the Haskell programming language.
An interesting consequence of the previously described safety features is how they shorten the feedback loop. The programming activity usually consists on iterating through the following activities:
The last step is often where the developers spend most time, and this time is either spent writing automated tests or interacting with the program to confirm that it is correct. Most programmers would say this phase to be the least joyful one.
Elm's safety and lack of runtime exceptions transfers part of the Testing phase effort into the Compiling phase. When using Elm, the programmer will spend a bit more time on the compiling phase - interacting with the compiler until the program is considered correct - and much less time in the testing phase, when the only thing that may fail is whether the business requirements are met - and not whether the program crashes or not.
When using Elm, the programmer will have feedback about his mistakes sooner, not having to spend so much time in the lengthy testing phase.
Elm's syntax is inspired by Haskell and minimalist. There are only two control statements; and very few reserved words.
The center of this syntax is the function. If the most used syntactic feature of any structured programming language is the function invocation, then why do most Algol-style languages resort to so many special characters for function invocation and definition?
In order to write the above definition and invocation, we had to type 8 special characters for the function definition:
and 4 on the function invocation:
Depending on your keyboard's language, the number of pressed modifier-keys; annoyance and premature repetitive-strain-injury will vary.
Elm cleans this up. If the function is such an important thing it should be easily typed and edited:
As you see, this function definition doesn't need any special characters. Type signatures are optional and automatically inferred by the compiler, so that we can swiftly experiment without having to explicitly enter them. Nonetheless, they are useful: sometimes it is handy to have the type signature as guidance while writing a less obvious piece of code, and they often are a useful form of documentation. Thus, in Elm, type signatures are (optionally) written in a separate line (some editors will generate the type signature automatically when we ask for it!):
Another feature that makes the language rather fun to use is that every function is curried and may thus be applied to a subset of its arguments, returning a so called partially-applied function that awaits only the missing arguments. Thus, the following code :
corresponds to the following Javascript code:
The main advantage of this feature is that code using higher-order-functions becomes very clean:
Though if we chain these operations we start to get a sea of nested parens typical from a Lisp:
To avoid this problem, the language provides the pipe operators (<|
and |>
). They allow chaining function application with a syntax akin to threading an argument through a pipeline of transformations:
In a similar fashion; you can also use the composition (<<
) or reverse-composition (>>
) operators to express the same without having to explicitly refer the argument that is being threaded:
This conveniently succinct syntax, together with the nice error messages end up making Elm very fit for interactive experimentation, both by using the REPL and by using the compiler.
When you start an Elm project you immediately get plenty of functionality out-of-the-box:
And it's worth elaborating on some other tools:
This is one of the things Redux got from implementing the Elm architecture. When developing and trying a program we are able to inspect the actions performed on that interface and to go back and forth in time in order to analyze the state of the program at each moment of its execution.
In Redux this functionality is rather fragile due to the imperative nature of Javascript, and in order to cope with it, one is required to use Immutable or another library to avoid directly mutating the state of the application. In Elm, this problem simply does not exist, because there's no such thing as mutation at the language level. The language design itself protects us from this kind of problems and from the need of using additional libraries.
Additionally, you may fiddle with the language directly on the web-browser - without having to install anything. The following is a good example of a site that provides live-editing functionality:
When publishing a new package version, the Elm tooling will check whether the functions that are exported by our modules:
According to each of these possibilities, the change will be reflected on the automatically calculated package's version number, that will let it clear whether the changes are major changes; minor changes or bugfixes; and whether it is safe to upgrade the version of a library that is being used.
In Part 2 - available here - I will talk about why static typing and functional purity given, the current Javascript's ecosystem trends. I will also talk about who's using Elm and how mature it is. Please stay tuned, as I'll be writing about it next week.
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!
Rails developer with 10+ years of experience with diverse technologies. I am interested in functional programming.
People who read this post, also found these interesting: