I've been having a look at the Elm programming language and the Elm framework. They are a typed functional programming approach to the frontend that is influencing other popular technologies in a somewhat disruptive way. This article is a writeup on what I have been figuring out about Elm.

This article will be split into two parts. In this first part I'll focus on the features that make Elm an interesting alternative to Javascript.

In the second part I will focus on the historical perspective that makes Elm worth a look; elaborate on the language's two main features: functional purity and static-typing; and finally, look at who is using Elm in the real world.

What is Elm?

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.

1. Elm is easy

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.

Friendly error messages

[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:

The 2nd argument to function `append` is causing a mismatch.

52| List.append listOfFloats [ "Banana" ]
                             ^^^^^^^^^^^^
Function `append` is expecting the 2nd argument to be:

    List Float

But it is:

    List String

Hint: I always figure out the type of arguments from left to right. If an
argument is acceptable when I check it, I assume it is "correct" in subsequent
checks. So the problem may actually be in how previous arguments interact with
the 2nd.

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:

Cannot find variable `map`

49| map 1 2
    ^^^
Maybe you want one of the following?

    max
    main
    min
    msg

No Typeclasses

[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.

2. Elm is Safe

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:

  • There are no runtime errors
  • There are no null values
  • Control statements are forced to deal with all possibilities

No runtime errors

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:

type Maybe a = Just a | Nothing

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.

No null

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.

All possibilities must be taken into account

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:

-- akin to an enum
type TypingDiscipline = Static 
                      | Dynamic 
                      | 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:

-- assuming x is of type TypingDiscipline
case x of

  Static -> "I feel safe."

  Dynamic -> "Let's check an horoscope"

the compiler does compile the program:

This `case` does not have branches for all possibilities. - You need to account for the following values:

    Gradual

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.

3. Elm is fun

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.

Short feedback loop

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:

  • Thinking - thinking about the code to write
  • Typing - typing the code
  • Compiling - fix the code so that it compiles
  • Testing - test if the code does what it was supposed to

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.

Uncluttered syntax

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?

function myFunction(arg1, arg2){ 
  functionBody();
}

myfunction(3, 2);

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:

myFunction arg1 arg2 = 
  functionBody

myFunction 3 2

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!):

factorial : Integer -> Integer
factorial n = 
  if n == 0 then 
    1 
  else 
    n * factorial (n - 1)

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 :

-- function with three args
add3 a b c = a + b + c 

{- function with two args created by partially applying add3 
   only to the first argument -}
add1to2args = add3 1

{- applying the previously created function to the remaining 
   arguments -}
add1to2args 2 3  --> 6 

corresponds to the following Javascript code:

//curried fuction with three arguments
add3 = (a) => (b) => (c) => {a + b + c}

/* function with two arguments created by applying 
   add3 only to the first argument */
add1to2args = add3(1)

/* applying the previously created function to the 
   remaining arguments */
add1to2args(2)(3) //=> 6

The main advantage of this feature is that code using higher-order-functions becomes very clean:

isBiggerThan x y = x < y

List.filter (isBiggerThan 5) [1, 2, 3, 4]

power x =  x * x

List.map power [1, 2, 3, 4] --> [1, 4, 9, 16] 

Though if we chain these operations we start to get a sea of nested parens typical from a Lisp:

List.filter (isBiggerThan 1) 
  (List.map power
    (List.filter (isBiggerThan 5) 
      [1, 4, 9, 16])) --> [81, 256]

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:

[1, 4, 9, 16]
  |> List.filter (isBiggerThan 5)
  |> List.map power
  |> List.filter (isBiggerThan 1) --> [81,256]

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:

functionWithPipe : List Int -> List Int
functionWithPipe arg =  
  arg
    |> List.filter (isBiggerThan 5)
    |> List.map power
    |> List.filter (isBiggerThan 1)

functionWithComposition : List Int -> List Int
functionWithComposition =  
  List.filter (isBiggerThan 5)
    >> List.map power
    >> List.filter (isBiggerThan 1)

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.

Nice tooling

When you start an Elm project you immediately get plenty of functionality out-of-the-box:

  • A rendering framework based on virtual Dom (as React)
  • A state container (as Redux)
  • The immutability that in JS is usually accomplished by using Immutable is part of the Elm language definition
  • Type checking - similar to - but stronger - than the one you may get by using Flow
  • An interactive development environment similar to what is provided by Webpack, called Elm Reactor
  • A package manager called elm-package

And it's worth elaborating on some other tools:

Time-traveling debugger

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.

Online editors

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:

  • Ellie, good for experimenting with more than one file and allowing the use of additional packages that do not belong in the core of the language.

Automatically enforced semantic version

When publishing a new package version, the Elm tooling will check whether the functions that are exported by our modules:

  • changed their signatures;
  • only exported new functions;
  • or did not change the signatures of the exported functions.

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.

To be continued

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!

Ready for a UX Audit? Book a free call

Found this article useful? You might like these ones too!