A Farewell to FRP

- making signals unnecessary -

by Evan Czaplicki / 10 May 2016

The Elm Architecture is a simple pattern for architecting web apps. So it is having success, but we still hear questions like: How can I use websockets in The Elm Architecture?

Well, Elm 0.17 is out today, and it introduces subscriptions which cover these cases in a really pleasant way. Subscriptions let components sit around and wait for messages while library code handles a bunch of tricky resource management stuff behind the scenes. Later in this post we will see how this makes websockets super simple to work with.

That is all nice, but the big benefit is that Elm is now significantly easier to learn and use. As the design of subscriptions emerged, we saw that all the toughest concepts in Elm (signals, addresses, and ports) could collapse into simpler concepts in this new world. Elm is designed for ease-of-use, so I was delighted to stumble upon a path that would take us farther with fewer concepts. To put this in more alarmist terms, everything related to signals has been replaced with something simpler and nicer.

There are two typical reactions to this news:

This is crazy. How will anything work?!
I'd estimate that 95% of code stays exactly the same, and the upgrade plan will walk you through the couple things you need to update. It is not actually a big deal.

What is this guy talking about? What is FRP? What are signals?
The cool thing about this release is that you do not need to know about that stuff anymore. Elm is just easier now.

What are subscriptions?

A subscription is a way for a component to sit around and wait for messages.

You can subscribe to the current time.

You can subscribe to location changes.

You can subscribe to web socket messages.

etc.

The simplest subscription is time. It is very typical Elm code, following The Elm Architecture:

subscriptions : Model -> Sub Msg
subscriptions model = Time.every second Tick
Based on the current model, we are describing all the active subscriptions. In this case, the Time.every function is setting up a subscription to the current time, updated every second. New times (like Tick 1462487781991) are fed into the update function, just like messages resulting from mouse clicks or HTTP requests.

So besides subscriptions, everything about The Elm Architecture is the same as before. It is just easier to passively wait for stuff. Let's see this in a more difficult scenario.

Web Sockets

Here is a simple chat client. To avoid people saying crazy things to each other, we have it pointed at a chat server that just echos your messages. It looks very similar to the clock example, but it is actually managing a web socket connection! All you have to think about are the following functions:

  WebSocket.send "ws://echo.websocket.org" input
  WebSocket.listen "ws://echo.websocket.org" NewMessage
The first one sends messages, the second one subscribes to messages. In both cases we provide the address of the chat server we care about and a value that helps us communicate with it. For send we just give it the string we want to send. For listen we have a tagger function, so when we receive a message like "hi" from the server, it is fed into our update function as NewMessage "hi".

Now the crazy thing is that we wrote a fully functional chat client without doing a bunch of shenanigans to set everything up. We send and we listen. That is it. I mean, it doesn't actually sound too crazy unless you know the two options you have in JavaScript:

  • Use the browser API
  • — To get started you just create a new web socket! Well, then you need to open the connection. But do not forget to add an onerror listener to detect when the connection goes down and try to reconnect with an exponential backoff strategy. And definitely do not send messages while the connection is down, that is a runtime error! You need to queue messages and send them later. So once you have your queue implemented, that's it! Now you just need to decide which component owns this web socket and make sure that the socket is closed at the appropriate time.

  • Use a JS library
  • — Using the browser API seems like trouble, so let's just find a library that does all that. Looks like we have more than 2000 options. Hmm... Hopefully one of the first few we try works well.

    In the WebSockets package for Elm, all of the babysitting of the connection is handled automatically. The connection is opened if anyone is subscribed to it, and it is closed if no one needs it anymore. All the queuing and reconnecting happens behind the scenes.

    Learning More

    Elm is about making delightful projects. Projects you are excited to share. Projects that get you excited about programming! That means I am always asking myself how Elm can be simpler. How can it be easier to learn? More fun? Quicker for prototyping? More reliable? I think my obsession with these questions are the heart of Elm's design philosophy and Elm's success.

    When I started working on my thesis in 2011, I stumbled upon this academic subfield called Functional Reactive Programming (FRP). By stripping that approach down to its simplest form, I ended up with something way easier to learn than similar functional languages. Signals meant piles of difficult concepts just were not necessary in Elm.

    I think anyone who has taught Elm recently would agree that signals are one of the few stumbling blocks left. They made Elm easier than its peers, but they did not make Elm easy.

    As The Elm Architecture emerged, it became clear that you could do almost all your Elm programming without thinking about signals at all. So the start-app package was an experiment to see what happens when we push signals way later in the learning path. The results were great! Folks were getting started quicker, making it farther, and having more fun! In the end, we had lots of folks who became excellent Elm programmers without ever really learning much about signals. They were not necessary.

    Elm 0.17 is just taking the next logical step.

    In the end, it was possible to remove signals because Elm has been moving towards an explicit emphasis on concurrency for quite some time now. The seeds for this are obvious in my thesis, but the wheels really started turning on this in Elm 0.15 when tasks were introduced. That release also introduced a scheduler that was able to switch between work whenever it wanted.

    Elm 0.17 improves this scheduler quite significantly, taking some basic insights from the BEAM VM used by Erlang and Elixir. You can read a tiny bit about how it works and where it is going in the Process module docs. This is also the foundation for effect managers, which make subscriptions possible in the first place. I hope to flesh out the documentation on this much more, but the nice thing is that you do not need to know this stuff to be an Elm expert. Just like with my thesis, Concurrent FRP, the goal is to get the benefits of concurrency for free.

    So is Elm about FRP anymore? No.

    Those days are over now. Elm is just a functional language that takes concurrency very seriously. And from a user's perspective, Elm is just a friendly functional language!

    Note: Unfortunately for me, I had no idea my thesis had so much in common with synchronous programming languages at the time, but the connections are quite striking. I might argue that Elm was never about FRP.

    What is Next?

    This release began with a vision of how Elm will handle "native" code. What would a healthy package ecosystem look like? Elm 0.17 is the foundation for this vision. We are now set up to support (1) everything in web platform and (2) all sorts of custom backends like GraphQL, Elixir Phoenix, Firebase, etc. All of these cases can be handled in terms of commands and subscriptions, creating a consistent experience across packages that works great with The Elm Architecture. I am excited to see this start rolling out in the coming weeks and months!

    But how to get there? Well, the web platform is not really that much stuff when you look at it seriously, so the @elm-lang organization will expand to cover the remaining APIs. This is best because:

    I do not expect to be compiling to JavaScript forever, especially with WebAssembly on the horizon. The smaller the interface between Elm and JS, the easier it will be to support other platforms.

    We do not want four okay versions of bindings to web platform APIs. One great version is better.

    I know some people are eager to help with creating these libraries. Please give me some time to develop a coherent process for making sure a desire to help can also translate into great results. In the meantime, the best way to make progress is to meet people on the Elm slack and learn what is going on in the community. (You should always do that before you start trying to make contributions!)