Functional Reactive Programming with Elm: An Introduction

Share this article

This article was peer reviewed by Moritz Kröger, Mark Brown and Dan Prince. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Elm is a functional programming language that has been attracting quite a bit of interest lately. This article explores what it is and why should you care.

Elm’s current main focus is making front-end development simpler and more robust. Elm compiles to JavaScript so it can be used for building applications for any modern browser.

Elm is statically typed language with type inference. Type inference means that we don’t need to declare all the types ourselves, we can let the compiler infer many of the types for us. For example by writing one = 1, the compiler knows that one is an integer.

Elm is an almost pure functional programming language. Elm builds on top of many functional pattern like pure views, referential transparency, immutable data and controlled side effects. It is closely related to other ML languages like Haskell and Ocaml.

Elm is reactive. Everything in Elm flows through signals. A signal in Elm carries messages over time. For example clicking on a button would send a message over a signal.

You can think of signals to be similar to events in JavaScript, but unlike events, signals are first class citizens in Elm that can be passed around, transformed, filtered and combined.

Elm Syntax

Elm syntax resembles Haskell, as both are ML family languages.

greeting : String -> String
greeting name =
  "Hello" ++ name

This is a function that takes a String and returns another String.

Why Use Elm?

To understand why should you care about Elm, let’s talk about some front-end programming trends in the last couple of years:

Describe State Instead of Transforming the DOM

Not long ago we were building applications by mutating the DOM manually (e.g. using jQuery). As our application grows we introduce more states. Having to code the transformations between all of them exponentially grows the complexity of our application making it harder to maintain.

Instead of doing this, libraries like React have popularised the notion of focusing on describing a particular DOM state and then let the library handle the DOM transformations for us. We only focus on describing the discreet DOM states and not how we get there.

This leads to substantially less code to write and maintain.

Events and Data Transformation

When it comes to application state, the common thing to do was to mutate the state ourselves e.g. adding comments to an array.

Instead of doing this we can only describe how the application state needs to change based on events, and let something else apply those transformations for us. In JavaScript, Redux has made popular this way of building applications.

The benefit of doing this is that we can write ‘pure’ functions to describe these transformations. These functions are easier to understand and test. An added benefit is that we can control where our application state is changed, thus making our applications more maintainable.

Another benefit is that our views don’t need to know how to mutate state, they only need to know what events to dispatch.

Unidirectional Data Flow

Another interesting trend is having all our application events flow in a unidirectional way. Instead of allowing any component talk to any other component, we send message through a central message pipeline. This centralized pipeline applies the transformations we want and broadcasts the changes to all the parts of our application. Flux is an example of this.

By doing this we gain more visibility of all interactions that happen in our application.

Immutable Data

Mutable data makes it very hard to restrict where it can be changed, as any component with access to it could add or remove something. This leads to unpredictability, as state could change anywhere.

By using immutable data we can avoid this, by tightly controlling where application state is changed. Combining immutable data with functions that describe the transformations gives us a very robust workflow, and immutable data helps us enforce the unidirectional flow by not letting us change state in unexpected places.

Centralized State

Another trend in front-end development is the use of a centralized ‘atom’ for keeping all state. Meaning that we put all state in one big tree instead of having it scattered across components.

In a typical application we usually have global application state (e.g. a collection of users) and component specific state (e.g. the visibility state of a particular component). It is controversial whether storing both kinds of state in one place is beneficial or not. But at least keeping all application state in one place has a big benefit, which is providing a consistent state across all components in our application.

Pure Components

Yet another trend is the use of pure components. What this means is that given the same inputs a component will always render the same output. There are no side effects happening inside these components.

This makes understanding and testing our components far easier than before, as they are more predictable.

Back to Elm

These are all great patterns that make an application more robust, predictable, and maintainable. However to use them correctly in JavaScript we need to be diligent to avoid doing some things in the wrong places (e.g. mutating state inside a component).

Elm is a programming language that has been created from the beginning with many of these patterns in mind. It makes it very natural to embrace and use them, without worrying about doing the wrong things.

In Elm we build applications by using:

  • Immutable data
  • Pure views that describe the DOM
  • Unidirectional data flow
  • Centralized state
  • Centralized place where mutations to data are described
  • Contained side effects

Safety

Other big gain of Elm is the safety that it provides. By completely avoiding the possibility of values being null, it forces us to handle all alternative pathways in an application.

For example, in JavaScript (and many other languages) you can get run time errors by doing something like:

var list = []
list[1] * 2

This will return NaN in JavaScript, which you need to handle to avoid a runtime error.

If you try something similar in Elm:

list = []
(List.head list) * 2

The compiler will reject this, telling you that List.head list returns a Maybe type. A Maybe type may or may not contain a value, we must handle the case where the value is Nothing.

(Maybe.withDefault 1 (List.head list)) * 2

This provides us with a lot of confidence in our applications. It is very rare to see runtime errors in Elm applications.

Sample Application

To get a clearer picture of the Elm language and how applications are built with it, let’s develop a tiny application that shows an HTML element moving across a page. You can try this application by going to http://elm-lang.org/try and pasting the code there.

import Html
import Html.Attributes exposing (style)
import Time

name : Html.Html
name =
  Html.text "Hello"

nameAtPosition : Int -> Html.Html
nameAtPosition position =
  Html.div [
    style [("margin-left", toString position ++ "px")]
  ] [
    name
  ]

clockSignal : Signal Float
clockSignal =
  Time.fps 20

modelSignal : Signal Int
modelSignal =
  Signal.foldp update 0 clockSignal

update : Float -> Int -> Int
update _ model =
  if model > 100 then
    0
  else
    model + 1

main : Signal Html.Html
main =
  Signal.map nameAtPosition modelSignal

Let’s go over it piece by piece:

import Html
import Html.Attributes exposing (style)
import Time

First we import the modules we will need in the application.

name : Html.Html
name =
  Html.text "Hello"

name is a function that returns an Html element containing the text Hello.

nameAtPosition : Int -> Html.Html
nameAtPosition position =
  Html.div [
    style [("margin-left", toString position ++ "px")]
  ] [
    name
  ]

nameAtPosition wraps name in a div tag. Html.div is a function that returns a div element. This function take an integer position as a unique parameter.

The first parameter of Html.div is a list of HTML attributes. The second parameter is a list of children HTML elements. An empty div tag would be Html.div [] [].

style [("margin-left", toString position ++ "px")] creates a style HTML attribute, which contains margin-left with the given position. This will end as style="margin-left: 11px;" when called with position 11.

So in summary nameAtPosition renders Hello with a margin on the left.

clockSignal : Signal Float
clockSignal =
  Time.fps 20

Here we create a signal that streams a message 20 times per second. This is a signal of floats. We will use this as a heartbeat for refreshing the animation.

modelSignal : Signal Int
modelSignal =
  Signal.foldp update 0 clockSignal

clockSignal gives us a heartbeat, but the messages it sends through the signal are not useful, the payload of clockSignal is just the delta between each message.

What we really want is a counter (i.e 1, 2, 3, etc). To do this we need to keep state in our application. That is take the last count we have and increase it every time clockSignal triggers.

Signal.foldp is how you keep state in Elm applications. You can think of foldp in a similar way to Array.prototype.reduce in JavaScript, foldp takes an accumulation function, an initial value and a source signal.

Each time the source signal streams an event, foldp calls the accumulation function with the previous value and holds onto the returned value.

So in this case, each time clockSignal streams a message, our application calls update with the last count. 0 is the initial value.

update : Float -> Int -> Int
update _ model =
  if model > 100 then
    0
  else
    model + 1

update is the accumulation function. It takes a Float which is the delta coming from clockSignal as first parameter. An integer which is the previous value of the counter as second parameter. And returns another integer which is the new value of the counter.

If the model (previous value of the counter) is more than 100 we reset it to 0, otherwise just increase it by 1.

main : Signal Html.Html
main =
  Signal.map nameAtPosition modelSignal

Finally, every application in Elm start from the main function. In this case we map the modelSignal we created above through the nameAtPosition function. That is, each time modelSignal streams a value we re-render the view. nameAtPosition will receive the payload from modelSignal as first parameter, effectively changing the margin-left style of the div twenty times per second, so we can see the text moving across the page.

The application we just built above demonstrates:

  • HTML in Elm
  • Using signals
  • Keeping state the functional way
  • Pure views

If you have used Redux you will note that there are several parallels between Elm and Redux. For example update in Elm is quite similar to reducers in Redux. This is because Redux was heavily inspired by the Elm architecture.

Conclusion

Elm is an exciting programming language which embraces great patterns for building solid applications. It has a terse syntax, with a lot of safety built-in which avoids runtime errors. It also has a great static type system that helps a lot during refactoring and doesn’t get in the way because it uses type inference.

The learning curve on how to structure an Elm application is not trivial, as applications using functional reactive programming are different to what we are used to, but is well worth it.

Additional Resources

  • When building large applications in Elm it is a good practice to use the Elm architecture. See this tutorial for more information.
  • The Elm Slack community is an excellent place to ask for help and advice.
  • The Pragmatic Studio videos on Elm are an excellent resource to get you started.
  • Elm-tutorial is a guide I’m working on to teach how to build web applications with Elm.

Frequently Asked Questions (FAQs) about Functional Reactive Programming in Elm

What are the key differences between Elm and other functional programming languages?

Elm is a functional programming language that compiles to JavaScript. It is known for its strong type inference, meaning you don’t have to annotate your code with types. Elm’s architecture is also unique, as it follows the Model-View-Update (MVU) pattern, which is different from the Model-View-Controller (MVC) pattern used in many other languages. Elm also has a friendly compiler that provides helpful error messages, making it easier for developers to debug their code.

How does Elm handle side effects?

Elm handles side effects differently from other languages. In Elm, all side effects are managed by the Elm runtime system. This means that Elm functions are pure and do not have side effects. Instead, they return descriptions of side effects, which the Elm runtime system executes. This approach makes it easier to reason about your code and eliminates a whole class of bugs related to side effects.

What is the Elm Architecture?

The Elm Architecture is a pattern for architecting web applications. It is built around a simple pattern of Model-View-Update (MVU). The Model represents the state of your application, the View generates HTML based on the state, and the Update function updates the state based on messages. This pattern makes it easy to structure your code and reason about how data flows through your application.

How does Elm handle errors?

Elm has a unique approach to error handling. Instead of using exceptions, Elm uses types to represent errors. This means that you have to handle all possible errors in your code, which leads to more robust and reliable software. Additionally, the Elm compiler provides helpful error messages that guide you to fix the problem.

What are the benefits of using Elm?

Elm offers several benefits over other languages. Its strong type system and friendly compiler help eliminate runtime errors, making your code more reliable. The Elm Architecture provides a clear structure for your code, making it easier to maintain and understand. Elm also has great performance, as it compiles to efficient JavaScript code.

How do I start learning Elm?

The best way to start learning Elm is by reading the official guide and trying out some examples. You can also check out various online tutorials and resources. Elm has a friendly and welcoming community, so don’t hesitate to ask questions if you get stuck.

Can I use Elm for large-scale applications?

Yes, Elm is suitable for large-scale applications. Its strong type system and architecture make it easy to maintain and scale your code. Several companies, including NoRedInk and CircuitHub, are using Elm in production for large-scale applications.

How does Elm compare to JavaScript frameworks like React?

Elm and React have some similarities, as both use a virtual DOM for efficient updates. However, Elm has several advantages over React. Elm’s strong type system and friendly compiler help eliminate runtime errors. Elm also has a simpler and more consistent design, making it easier to learn and use.

What is the future of Elm?

Elm is a young language, but it has a vibrant and growing community. The language is being actively developed, with new features and improvements being added regularly. With its unique features and benefits, Elm has the potential to become a major player in the world of web development.

Can I use Elm with existing JavaScript code?

Yes, you can interoperate with JavaScript code using Elm’s ports. Ports allow you to send and receive messages between Elm and JavaScript. This makes it possible to gradually introduce Elm into an existing JavaScript codebase.

Sebastian PortoSebastian Porto
View Author

I'm a web developer always trying to find, explore and share the best tools for the job. I love coding, learning and teaching.

elmfunctional programmingimmutabilitylanguages that compile to JavaScriptnilsonjReactive Programming
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week