Looking at the Elm Language as an AngularJS Developer
Over the last several years, there has been increasing discussion of functional programming, both abstract concepts and concrete language implementations. While JavaScript can support functional programming concepts, the AngularJS framework does not provide a purely functional approach. If you’re not familiar with functional programming, we’ll quickly explore some of the basics before looking more closely at the Elm language.
And then sum
Functional programming is a paradigm that prioritizes reliable, stateless code that has no side effects. For example, let’s define a JavaScript function called “sum” as accepting two inputs “x” and “y” and returning the result of “x + y”.
function sum(x, y) {
return x + y;
}
console.log(“We have “, sum(1,2));
We can say this code is reliable, or safe, because every time we call this function with a set of inputs, it will always return the exact same result. In other words, calling “sum(1, 2)” always returns 3, no matter the number of times we make the call nor when we call it in the program.
The code is stateless because the entire operation depends solely on the inputs to the function. There are no other variables, global or otherwise, outside of the function scope. And there are no side-effects because the code in the function makes no changes to any of the inputs; it simply performs its logic and returns the result.
Now let’s look at an example that breaks these assumptions.
var total = 0;
function add(x) {
total = total + x;
}
add(3);
console.log(“We have “, total); // We have 3
add(3);
console.log(“We have “, total); // We have 6
Here, we see that the function isn’t stateless and is vulnerable to side-effects; the value of “total” is defined globally and can be changed by code outside of the add function. We don’t (usually) write this kind of code in AngularJS. However, if you’ve ever worried about variables defined in the $rootScope being updated across several controllers, you’ve experienced the pain that functional programming is designed to address. Understanding these concepts will help you write better JavaScript code, even if you never use a single line of Elm in production.
Talking Elm
Now that we have a working understanding of functional programming, we can talk more about the Elm language. Elm is front-end focused, typed functional language which transpiles to JavaScript. (The Elm compiler creates JavaScript code based on the Elm code it is given.) It’s designed to be easy to use, with common-sense naming schemes and helpful error messages to allow a developer to be productive quickly.
While Elm has some JavaScript interoperability, it’s intended to replace a JavaScript framework like AngularJS.
Elm comes with a number of useful platform tools. The first is a REPL (Read, Evaluate, Print, Loop) command line interface. It is available online or as a locally installed utility as part of the Elm pl. This makes it easy to try out a few lines of Elm code and see what happens.
For example, if I type “2” and hit the enter key, the REPL will do this:
My input was read and evaluated, and the result was printed to the screen. The result and the data type of that result are both shown, so that I can see what the compiler has come up with.
Making Changes
Another Elm tool is called Reactor. If you’ve ever used gulp tasks to watch for file changes, rebuild, and reload your app in the browser, then you have a good understanding of what Reactor does. Where the REPL lets us try out Elm a few lines of code at a time, Reactor fills the role of a local development server.
Finally, Elm has a package system, similar to Bower or NPM, which offers automatically enforced semantic versioning. With Elm packages, code changes that break an API or introduce new values are marked with significant version number changes. This prevents seemingly minor version number changes (1.0.0 to 1.0.1) from breaking your existing application code without some warning. For more about this idea, check out http://semver.org/.
As we mentioned before, Elm is a typed functional language. Strong typing allows the compiler to infer information about the code, including legal and illegal arguments, as well we what you, the programmer, might have intended to do. For example, we can try some simple addition, adding 2 + 2 in the REPL.
The result is what we expect: 2 + 2 equals 4 and 4 is a number. But what we if mistype and use “++” instead of “+”?
Here we find that numbers aren’t valid arguments for the append operator “++”. The compiler shows us where the error is, and tries to give us some hints based on what it thinks we intended. And because this was caught in the compiler, it will never be seen as a runtime error in the application. This may not seem like a very big deal at first, but consider what happens when JavaScript is put in a similar situation.
The “+” operator is overloaded, and acts as either addition for numbers or concatenation for strings. In this case, the string concatenation function is used, giving us a result that isn’t what we expect. And while we might never write 2 + “2”, we’ve probably all seen something more like this:
The values of x and y have different types, but JavaScript is happy to do exactly what it thinks we told it to. The result, “22”, is neither mathematically correct, nor of the data type that we intended. We have at least one logic bug and possibly several runtime exceptions that we may not find until later on. In Elm, we explicitly declare what data type x and y are, and this allows the compiler help us identify where potential bugs may be creeping in with data that doesn’t match the correct type.
Here I’ve defined the data type Point to be an x and a y value, which must both be integers. When I follow the type definition in my declaration and type “Point 1 2”, the compiler is happy. However, if I try to slip in a string as the second argument, the compiler throws a type mismatch exception.
Mapping the Elm Code
Now that we’ve seen how the compiler interprets our code and provides helpful error feedback, let’s take a look at larger example. I won’t be going deep into the specific syntax of Elm, as there is an entire set of tutorials which explain those concepts. Instead, we’ll discuss how to mentally map the Elm code to the Angular controller code we’re familiar with. We’ll be looking at the field example page.
The goal of the program is to print out the reversed string of the text entered by the user, updating every time the input changes. Here’s a listing of the code.
The first block of code contains the import statements, which provide the dependencies for the program. The “exposing” keyword takes of list elements in the package that you want to make available within the program.
Next is the main declaration, where we initialize the program with the model, view, and update, which are defined below. The model holds the content property. In an Angular controller, the $scope defines what functions and data will be available to the view. Here, the model is given a specific type of Model, which designates that a model must have a content property, which may only contain a String.
The update block is similar to an Angular controller, as it is where we put the application logic for this small program. In Elm, the update function listens for messages. It then responds to the message based on what the message is and the application logic found in the code. Here, the update function has a case for the Change message, which accepts an argument called newContent. If you look at line 26, you’ll see that the newContent argument must be a String. If we try to pass some other data type, we get a compiler error, not a runtime error, as we discussed before. At line 32, we can see that the model “object” – which is called a record in Elm terminology – will be updated. The content property is assigned the value of the newContent argument.
How’s the view?
The final block is the view. In Elm, view functions like this replace the HTML code. It’s very similar to using an inline template in an Angular controller. All of the HTML elements used here are provided by the Html import at the beginning of the program. Because the view is Elm code, instead of another language like HTML, we have access to all of the Elm functions and packages that we’ve imported into our program. This occurs without having to specifically wire things in via the $scope. In line 41 we can see that in order to achieve our text reversal goal, we’ve simply called the String.reverse function and given it the content property of our model.
For comparison, here is a simple Angular approach to solving the same problem. You can see that the same ideas are here: we have a model for our application data, some program logic, and a view.
Today we’ve covered some basics about what functional programming is. And we looked at how we can bridge our understanding of AngularJS applications to Elm. If you want to learn more, I strongly recommend checking out the Introduction to Elm at https://guide.elm-lang.org/, as well as the syntax documentation. The author of the language, Evan Czaplicki, also gave a talk titled “Let’s be Mainstream! User-focused Design in Elm” that provides some additional background on the design decisions taken when developing the language and how those can help make Elm easier to adopt.