RailsConf 2017

Developer Happiness on the Front End with Elm

Developer Happiness on the Front End with Elm

by Kevin Yank

In his talk "Developer Happiness on the Front End with Elm," presented at RailsConf 2017, Kevin Yank discusses how Elm, a functional programming language that compiles to JavaScript, can enhance developer happiness in front-end development. The session highlights the emphasis on developer experience that both Ruby and Rails exhibit and compares it to Elm's unique approach with its compiler and language design.

Key Points:
- Introduction to Developer Happiness: Yank begins with the concept of developer happiness, citing practices in Ruby and Rails that prioritize making programming a pleasant experience.
- The Shift from JavaScript to Elm: The speaker notes the limitations of JavaScript, particularly its growing complexity and the difficulties it presents for developers, prompting an exploration of alternatives.
- Elm's Compiler: Elm features a 'lovable compiler' that provides extremely helpful error messages, aiding developers by clearly indicating issues and suggesting fixes. For example, the compiler can point out type mismatches with insightful feedback.
- Radical Simplicity of Elm: Elm is designed with simplicity in mind. It has a batteries-included philosophy, which simplifies the development process by providing necessary tools packed together. This is contrasted with the complexity of the JavaScript ecosystem.
- Functional Programming Benefits: Yank emphasizes the advantages of Elm’s strict type system and immutability, which prevent many runtime errors found in JavaScript. The language forces developers to handle different data possibilities explicitly, improving code safety.
- Practical Example: Yank illustrates Elm's syntax by walking through a simple application where features can be added easily, showing how the compiler guides the developer step by step throughout the coding process.
- Comparison with Rails: The talk concludes with a comparison of Elm’s approach to front-end development with Rails' back-end philosophy. Both prioritize developer needs but differ in their technical execution, with Elm focusing on static typing and simplicity.
- Integrating Elm with Rails: Yank briefly mentions that integrating Elm with a Rails backend shares similar plumbing as integrating other JavaScript solutions, inviting further discussion in an upcoming session.

00:00:13.200 All right, so I think we'll make a start as the last few people trickle in. My name is Kevin Yank, and this is Developer Happiness on the Front End with Elm.
00:00:18.359 I think we are all gathered here today, at least in part, because the Ruby programming language was designed to make programmers happy. As he touched on in his keynote yesterday, DHH, when he describes the design objectives for the Rails framework, he puts optimizing for programmer happiness right there at the top.
00:00:34.719 In his article, the Rails Doctrine, DHH describes what he means by developer happiness and gives us a few examples. The one that stood out to me was the array accessors. We all know about array.first and array.last. Well, Rails adds array.2, array.third, array.fourth, and array.fifth, and for good measure, it throws in array.42. There is no reason for this feature to exist except to make programmers like us smile when we find it in the documentation.
00:01:02.920 So that's what I think of when I think of programmer happiness. Now, I work at a company called Culture Amp down in Melbourne, Australia. We build a big web app that companies like Adobe, Airbnb, and Slack use to measure their company culture, collect feedback and data, and drive positive change in their workplaces. At Culture Amp, we take our company culture very seriously, as well as our development culture.
00:01:27.759 We value programming languages that make programmers happy, and for that reason, we made the choice early on to use Ruby. More recently, we've been adding some Elixir to our back end as well, because these are languages that make programmers happy. On the front end, we chose React not too long ago, and until about six months ago, we had three teams all building their UI in React.
00:01:57.439 The thing about React is that it's a JavaScript framework, and this is probably in the top 10 list of cheap conference slide laughs. What I'll add to this unoriginal observation is that, yes, the book on the right is actually getting bigger with new additions to the language in ES 2015 and later. JavaScript is getting better; it's getting some nice programmer happiness features. However, the thing about JavaScript is that it's part of the web platform, so you can never take anything out of JavaScript. The book on the left is getting thicker at the same rate.
00:02:45.920 For all its strengths— and I will certainly acknowledge that JavaScript is an amazingly versatile and powerful language— it is not, I think, a language designed for programmer happiness. For that reason, when it came time to light up a fourth project team at Culture Amp, we were open to other possibilities. The possibility we decided to experiment with was Elm.
00:03:11.400 In this talk, I'll give you a brief introduction to Elm and then we'll dive into some things about Elm that I think are great sources of developer happiness. I'll break these into two big categories: the lovable compiler and Elm's radical simplicity. We'll drill into each of these a little bit, and I'll wrap it all up by coming back to why we're all here today.
00:03:41.920 So let's go for an introduction to Elm. It's my distinct pleasure to provide, I hope, the first real glimpse at Elm for many of you in the room today, and that makes me very happy. Elm calls itself a delightful language for reliable web apps, and I think a lot of hay is made of that word 'reliable'. If you've heard about Elm, you've probably heard of its claim of no errors at runtime. But we're here to talk about developer happiness today, so let's focus a little more on the delightful aspect.
00:04:10.680 Elm is a language that compiles to JavaScript. When you write an Elm app, you'll create a number of modules in separate files and feed those files to the Elm compiler. What comes out the other end is a big blob of JavaScript ready to run in a web browser. When you write an Elm app, you will follow an architectural pattern called Elm architecture. All Elm apps in practice are structured this way. You start by writing an init function, which generates the initial model for your app. When I say model, I'm not talking about an Active Record model that's a single entity in your database; this is a complete model of the current state of your front end as it's running in the browser.
00:05:35.000 Next, you write a second function called 'view' that takes that model, whatever it is at any given time, and generates the HTML interface for your web app. If you're familiar with something like React at all, you might know that it lets you generate a virtual DOM. Every time your front end is rendered, it calculates the entire user interface and then the React framework takes responsibility for intelligently and efficiently updating just the changes in the actual browser. Elm does the exact same thing; it has its own virtual DOM implementation, so you get all those same benefits. As part of your interface, you'll declare the events you're interested in, and if the user does something like clicking a button, the Elm runtime will send your program a message.
00:06:40.839 You need to write a third function to handle these messages, and that will be your update function. Its job is to take the incoming message and your program's existing model and generate the new updated model, which the Elm runtime dutifully feeds to your view function to generate the updated interface. Your program ticks along through this cycle; that's how Elm programs work. Let me show you what this looks like in code. Over here, we're going to start by gluing together all the parts of our program. I've got a main function here, and I'm calling 'HTML.beginnerProgram'. I'm saying my model will be provided by my init function, my update function is called update, and my view function is called view.
00:07:57.160 We will start by writing that init function. It will generate our initial model, which is just a hash, or as we call it in Elm, a record with a field called 'count' and a value of zero. Then, we'll write our view that takes that model and generates our interface. That will be a div with no attributes. Inside that div, I'll have some text which will be the count from my model, and then I'll have an HTML button that generates an 'increment' message. Inside the button, we’ll have the text 'increment'. I’ve just made up a name for that increment message, so I need to tell Elm what that is. My message type will be a new type that can only be one thing: 'increment'. I then write my update function that receives that increment message and my current model, producing a new model which is a copy of the existing model but with the count updated to count plus one.
00:09:46.760 And there we go! We have a complete Elm program. Whenever you click that button, the number will go up by one. I’m only lying to you a little bit here regarding the formatting of this code; I've formatted it so it's easy for Ruby developers and JavaScript developers to read. In practice, Elm code is usually formatted this way, so it's quite a bit taller; Elm likes its vertical whitespace.
00:10:01.480 The other thing you’ll see is the weird commas: in both of the lists, the commas instead of being at the end of each line are at the beginning of the subsequent line. Elm does this to get really clean diffs, letting you add new entries to the lists without modifying any of the existing lines. Languages like JavaScript and Ruby do this by supporting trailing commas; Elm prefers to do it this way. I have seen the argument made that if you forget to type a comma, it’s a lot more obvious when your code is formatted this way.
00:10:42.279 I have to admit this was a sticking point for me when I first started playing with Elm. I thought I could get on board with reading code in this format, but there's no way I'm going to be able to train my old fingers to type code in this format. The good news is you don't have to learn to type code in this format because if you're writing any amount of Elm code, you're probably going to use a tool called Elm format. This tool integrates right into your editor, and every time you save your changes, it will snap your code into this community guideline approved format. There are no configuration options, so there are no Rubocop linter rule wars to contend with in your teams.
00:11:48.880 It’s really satisfying just to apply the tool and follow the rules, and it takes care of the formatting for you. This actually provides a really satisfying moment of developer happiness when you're first learning the language. You often find yourself typing a lot of code, sitting back in your seat thinking, 'I'm not sure this is quite right', and then when you save your changes and the code snaps into place, you get that nice feeling of validation that what you typed— at least syntactically— makes sense. That was the first experience of developer happiness that I had with Elm.
00:12:58.720 So that's a brief introduction to Elm and what it looks like. At this point, we'll delve a little deeper into sources of developer happiness that you get with Elm, the first being Elm's lovable compiler. If you've heard much about Elm, you've probably also heard that it has a reputation for great error messages.
00:13:10.680 I'm here to tell you today that Elm has the most amazing error messages I have ever seen in my career. Here's an example of an Elm error message: it says 'cannot find variable listn', shows my line number, shows my actual line of code, and underlines the part it's objecting to. It even goes on to say 'list does not expose nap; maybe you want one of the following', and its first guess is actually right: I meant to type 'list.map.'
00:13:41.640 This is what errors look like at the command line, but if you’re like me, you probably have a nice editor with Elm language integration. This is what error messages look like in Atom, for example. You get them as popovers when you hover over the red underlined code. They’re very nice. Elm errors are also extremely specific; for instance, it might say, 'the second and third branches of this if produce different types of values.' The second branch has the type 'string', but the third is a 'float'.
00:14:27.360 The error message is not only telling us what we gave it and what it expected, but it’s also explaining why it had that expectation and even how the language is designed to create that expectation. This is really helpful, especially for those first learning the language. Speaking of beginner-friendly errors, here I’m trying to concatenate two strings using the plus operator, but that’s not how you do it in Elm. Elm uses a plus-plus operator, and the hint at the bottom tells you that to append strings in Elm, you need to use the plus-plus operator instead of just plus.
00:14:53.840 There's even a hyperlink to the relevant documentation. So when you get something wrong as a beginner, as often as not, the compiler will guess what your intent was and give you a pointer to the correct thing to do. Did anyone happen to spot the bug in the program we wrote before? The Elm compiler spotted it, but I won’t read out this error message since it’s not particularly great.
00:15:14.960 It's pointing to this part of my code as the source of the problem when, in fact, the bug is located down here: I’m passing model.count, which is a number, to the text function, which expects a string. The reason Elm cannot pinpoint the source of that error is that it doesn't have enough information. I’ve given it conflicting details, and all the best it can do is point to the part of my program where those conflicting elements are smashing together.
00:16:10.480 The issue is that I've written an init function that returns a count that's an integer, and I've written a view function that expects a count to be a string. Elm is a strictly typed language, meaning the compiler decides for you what types all the values in your program are. If it finds inconsistencies, it gives you an error like this one.
00:16:52.080 What you can do to get a better error message is to give Elm more to work with by providing type information. I can declare this type alias called 'model', indicating that a model is a record containing a 'count' field that's an integer. Upon doing this, I can then annotate my init function and say it returns a model. The compiler checks out: that matches my expectations. I can annotate my view function to take a model and return HTML.
00:17:17.760 The compiler will then catch mistakes and provide a much clearer error message. Now it tells me, 'the argument to function text is causing a mismatch: function text expects the argument to be a string, but it received an int.' That makes fixing the bug super easy. I just need to wrap my model count in a call to toString, and we’re done!
00:18:01.360 So Elm has great error messages, especially when they're powered by static types, and you provide the necessary amount of information for the compiler to understand your expectations about the types flowing through your programs. Let's talk a little more about static types. When I showed you this bug, you might justifiably have thought, 'what kind of modern language is this that it can't automatically convert an integer to a string when it needs to?'
00:18:22.440 To explain why this is the case, I'll give you an example of a subtle bug that Elm's strict type system protects us from. Imagine a search field on our site with a hyperlink suggesting repeating the most recent search by clicking the link. If I were to implement this in JavaScript, I might have code where my search history is in an array, and I output a string using string interpolation to include the first element of that array. The problem arises if I've never conducted a search on the site; the array could be empty, which causes the user interface to display 'undefined'.
00:19:23.960 JavaScript, while functioning as designed, will convert that 'nothing' into a string and display it for users to see, resulting in a particularly insidious kind of bug that's hard to identify. You won't find this kind of issue in error tracking software; it will simply be delivered to your users as business as usual until you come across it yourself. I took a screenshot of Culture Amp's JavaScript error tracking system just a couple of weeks ago, and every single error relates to a null or undefined value making its way through our system and exploding when we attempt to use it.
00:20:34.120 The individual responsible for this situation is Tony Hoare, who invented the null reference in 1965. In a recent conference talk, he dubbed this his 'billion-dollar mistake'. At that time, he was designing the first comprehensive type system for references in an object-oriented language and thought he could include a null reference simply because it was easy to implement. Unfortunately, this has led to innumerable errors, vulnerabilities, and system crashes which likely have caused a billion dollars of pain and damage in the last 40 years.
00:20:54.960 Let’s compare the situation to the equivalent Elm program. In this case, I’m performing string interpolation to take the value returned by list.head and place it in the string. However, this Elm program will not even compile because the compiler checks the values' types and raises an error, saying it's expecting an 'appendable'. It informs me that the left argument is a 'maybe string', which means it’s either a string or nothing due to the design of the language.
00:21:31.760 In Elm, the type 'maybe' indicates that the value could be present or absent. Because list.head is not guaranteed to return a value every time, the compiler forces me to handle both scenarios. I can accomplish this by employing a case statement, similar to a switch statement in Ruby: if list.head of the search history returns 'just' the latest search, I can use the latest search in my string; if it returns 'nothing', I'll display an alternative string.
00:22:05.439 By adjusting the code in this way, the Elm compiler enforces that all cases are handled, so you have to consider both outcomes— this thoroughness ensures robustness in your application. The importance of this characteristic is underscored by Elm's strong static type system, which creates a guarantee of no errors at runtime due to its structure where 'nil' is not an option.
00:22:41.720 To wrap up this section of the talk, let’s explore what it’s like to add new features in an Elm app. We'll go back to the program we've been working on. Let’s add a new feature by introducing a second button. This button will be called 'decrement', generating a decrement message. Right away, I've got a red underline because the compiler doesn't recognize what 'decrement' means since it's a new identifier.
00:23:12.960 That’s easy to fix; I just go to my message type and indicate there are now two possible messages. The compiler indicates that I need to update my update function to accommodate the new case. Once I do, the Elm compiler points out any incomplete patterns; at this point, I can implement the new functionality without worrying about having missed anything because the compiler provides guidance every step of the way.
00:23:54.960 The lovable compiler that you get with Elm is like a perfect pair programming partner, always guiding you to the next step you need to move forward instead of complaining about the changes you made. That is one of the two big sources of developer happiness for me in Elm: the other is Elm's radical simplicity. Elm embraces a 'batteries included' philosophy, and as Rails developers, we are quite familiar with this.
00:24:41.720 Rails packages a range of tools that come preconfigured to work seamlessly together, eliminating the need for deep individual learning about each tool. This approach is surprisingly rare in the JavaScript ecosystem, which has been plagued by what is now commonly referred to as 'JavaScript fatigue', with dozens, if not hundreds, of cleverly named frameworks, languages, and development tools that developers must learn and configure individually. To attempt getting into JavaScript development can be a nightmare for this reason.
00:25:22.559 Looking at npm, the Node Package Manager, illustrates this problem. The orange line indicates npm, while the blue line represents Ruby gems, revealing that JavaScript's issues are about four times as significant. When I first immersed myself in Ruby, I was daunted by the number of cleverly named gems, let alone for JavaScript. In Elm, we alleviate this stress by offering a highly interconnected suite of solutions—all designed to function together with a single installer.
00:26:12.760 There’s just one website and one tutorial to follow, and the difference in ease of onboarding is remarkable. As Rails developers, we can empathize with this paradigmatic approach, especially as we observe how quickly junior developers can become productive in our Elm codebase compared to our React/Redux architecture, which often bamboozles newcomers for weeks.
00:26:57.240 Evan Zepke, the creator of Elm, likely should have been mentioned sooner in this talk. In the official Elm tutorial on the website, he states, 'forget what you've heard about functional programming: fancy words, weird ideas, bad tooling.' This opinionated perspective indicates that Elm needn't be more complex than it has to be; in effect, Elm is a functional programming language designed to eliminate unnecessary complications.
00:27:32.080 To illustrate through an interesting feature of functional programming languages like Elm, we can discuss a process called partial application. I’m declaring a function named 'add', which sums its two arguments and returns the result. This function can enable partial application! For example, by passing just 'one', I can accept another number later, allowing me to create a new function 'increment' that adds one to its input.
00:28:16.080 In functional programming discussions, this feature is also known as 'currying', though Elm community prefers to call it 'partial application' for clarity. Conversely, features that are unnecessary are intentionally eliminated from Elm in each subsequent release. A recent example from before Elm 0.18 was a syntax allowing you to wrap a function in backticks, resulting in formulae that felt like English, though it caused confusion for novice developers.
00:28:56.080 With that said, we recently removed that syntax as it was deemed unnecessary because it's easy to use Elm's piping operator; this allows for seamless code readability without requiring backticks. This brings us back to the fundamental design of Elm: it's a language built around immutable data and pure functions, greatly contributing to its simplicity. This means in Elm, when you change a value, you can only generate a new one, the original remains untouched.
00:29:43.280 If I give you an example within Rails, when I call user.update params, most people in this room would recognize that this modifies the user object in place. If I need to keep the original, I must make a copy beforehand. In contrast, Elm's immutable data structure ensures that when operating on values, the originals aren’t modified; instead, a new modified value is generated, ensuring clarity.
00:30:30.560 You can invoke your update function and trust that it won’t modify your user object since Elm functions cannot have side effects. At the same time, this means each Elm function is pure; a function merely computes its output based solely on the inputs provided. If you supply the same inputs, you will receive the same outputs, while also ensuring that the function cannot affect anything outside of it.
00:31:00.320 Of course, you might be wondering how Elm handles the necessity of performing actions like making Ajax requests. Elm can indeed do that, but it expands the understanding of Elm architecture. You might start with simple 'HTML.beginnerProgram', which provides a basic Elm architecture without any outside world interaction. However, when you’re ready to incorporate such functionality, you can switch to 'html.program', and it will guide you in what changes must be made.
00:31:48.480 This advanced architecture requires an init function creating your initial model with the option of returning a command to the Elm runtime—an example being an Ajax request. When you execute the command, the Elm runtime seamlessly manages this interaction while your render function only updates the view according to the model's state.
00:32:24.320 Upon successfully receiving a response from the request, the Elm runtime will signify the occurrence by sending a message, similar to clicking a button within the interface. You'll then process it as you would any other message in your update function, attending to both success and potential error cases. This handling is accompanied by the assurance that all functions remain pure, solely generating commands, while the Elm runtime interprets them into actual actions.
00:33:06.480 To summarize Elm's processes, even when submitting an Ajax request, you construct pure functions to generate commands. When you return those commands to the Elm runtime, it is what initiates the physical request. Consequently, these functions can be called freely without triggering any side effects, making them simple to test and conducive to a clean testing environment.
00:34:03.000 Finally, let's look at the comparative benefits of Rails and Elm. Both environments are marked by their 'batteries included' philosophies and are committed to providing clean syntax for developers. However, there are trade-offs; Ruby on Rails, being a dynamic, object-oriented, and very flexible language, fits perfectly for back-end programming. This allows for general-purpose usage, but on the front end— within the unpredictable realm of today's browsers— simplicity rules the day.
00:34:56.500 Elm was designed with this trade-off in mind; it’s static, functional, and relentlessly simple. This design empowers Elm to offer guarantees that instill development confidence and contribute to overall developer happiness. To finish up, I’d love to highlight one clear win: Elm's outstanding error messages; I think every other language could learn from this approach to error handling. I would love to see Ruby adopt some of these features.
00:36:14.440 If you’re curious about how we can bridge the gap between Rails and Elm, I'll refer you to my colleague Joe Cranford's talk this afternoon. She will discuss React on Rails and the journey taken to include a rich front end in an existing Rails application with the mechanics of integrating React; the same principles are applied to Elm. If you're interested in figured that out, please attend that discussion. I'm sure it'll be great, and I will be here, too.
00:36:45.440 With that, I want to thank you for your attention and am open to any questions.