There are many more examples, so you can
continue to explore Elm and experiment with hot-swapping.
Perhaps the most interesting thing about adding hot-swapping to Elm was that it
was quite easy. It took about four days. In that time, it became very clear that
the practicality of supporting hot-swapping was directly related to the abstractions
(or lack-thereof) in the language. This has direct implications for the future of
Interactive Programming in languages like JavaScript, Clojure, Elm, etc.
How hot-swapping works in Elm
Elm uses signals to represent values as they
flow through the program. You can think of a signal as a value that changes
over time or as a stream of events. Every Elm program sets up a network for
processing these signals, called a signal graph. Watch the following video
to understand signal graphs and how they can be used for hot-swapping:
Huge thank you to Laszlo for working on the
debugger demoed above.
For tons more details on signals and signal graphs, see
this thesis
or this paper.
Okay, so our signal graph is a bunch of nodes. Each node is associated with
some state and a pure function. To implement hot-swapping in Elm, you must:
- Compile the new program, resulting in a new signal graph.
- Copy the state over from the old signal graph.
That’s it.
This ensures (1) that our functions have been fully updated and (2) that all
of the state has been preserved. The process is fairly simple because Elm
has language features let us safely make many simplifying assumptions.
But even with optimal language features, hot-swapping is still impossible
in some cases.
Pushing the limits of hot-swapping
It is possible to change a program so much that it is no longer compatible
with previous versions. If we try to hot-swap with incompatible code, it will
lead to runtime errors. The programmer will be left wondering if their new
code introduced a bug or if it was just a case of bad hot-swapping. Perhaps
they start hunting for a bug that does not exist. Perhaps they ignore a bug
that does exist. This is not Interactive Programming, this is a buggy IDE.
To make hot-swapping reliable, we must know when programs are incompatible.
The more precise we can be, the more reliable hot-swapping can be.
There are two major categories of incompatibilies:
The API has changed. If the types of the arguments to a
function change, it is no longer compatible with the rest of the program.
It is not possible to copy the old state into the new program.
Perhaps there are now many more values and it is unclear how they relate
to our new program. Perhaps functions are tightly coupled with state, making
it hard to change either independently.
The ability to diagnose and respond to these issues depends on the language
you are working with. We will now see how both cases can be addressed with
features like static types,
immutability and purity,
and predictable structure.
Static Types
It is quite common to change an API. Modifying or extending existing functions
often requires it. For example, the following two functions conceptually do the
same thing:
distance (x,y) = sqrt (x^2 + y^2)
distance point = sqrt (point.x^2 + point.y^2)
But these functions are not interchangeable because their types do not match.
One works on tuples and the other on records. It is likely a couple other
functions would need to be modified to get everything working again.
In a dynamically typed language like JavaScript, deciding if it is a good idea
to try to hot-swap cannot be fully automated. The programmer must decide.
If we want true Interactive Programming, we need to do better than this.
In a statically typed language like Elm, all type errors are found
automatically. The hot-swapper can wait until the entire program type checks before
trying to swap the new code in. The hot-swapper can also check to see if the
types of the old state matches the types of the new state, ruling out a whole
class of hot-swapping-induced runtime errors. With static types, hot-swapping
can be safely automated to a much greater extent.
Immutability and Purity
Mutable state permits a tight coupling between state and functions.
Suddenly part of our programs state lives in functions. We cannot
just copy state across; we must examine all old functions to see if
they have any hidden or shared state. From there we must figure out how
that hidden or shared state connects to our new program, which may not
always be possible.
Elm makes hot-swapping easy by having a very clean separation between state
and functions. When we copy over the old state, we know that there is no shared
or hidden state that we are missing. This is made possible by the following
two features:
Both have already been very successful for concurrency in languages
like Erlang, one of the few languages that also
supports hot-swapping.
Predictable Structure
In Elm, the structure of signal graphs is known as soon as the program starts
and does not change. Elm’s static signal graphs are possible because
Elm does not permit signals-of-signals.
Many other frameworks for Functional Reactive Programming (FRP), particularly in
imperative languages, allow signal graphs to change over time. With
dynamic signal graphs, programmers can create and destroy nodes as they see fit.
Unfortunately, when the structure of the signal graph no longer matches the the
initial structure, hot-swapping becomes very difficult. How do you copy state from
the old signal graph to the new one when the graphs are not the same?
The issues with dynamic signal graphs are just a subset of the issues that come
up in languages without any support for FRP at all. When a program is just a
spaghetti soup of callbacks and shared state, figuring out how the initial program
relates to the current program must rely on much more complicated program analysis.
I am not saying it is impossible, but figuring this out would at least earn you a PhD.
The future of Interactive Programming in Elm
Elm makes many language-level decisions that make hot-swapping easy
and reliable. Many of the toughest problems with hot-swapping are solved
or avoided when you have features such as static types, immutability,
purity, and static signal graphs. Yet even with those features, there are
still some open technical questions:
Programmers can change the structure of a signal graph in their code.
Can a hot-swap ever be performed when the signal graph changes? Perhaps in
a limited subset of “safe” changes?
Even in a pure language, it is possible to associate state with functions by using
continuation passing style (CPS).
This comes up in the Automaton library,
which is an alternate way to write reactive code. Is it possible to persist state and
update functions when using CPS?
There are also some fun questions:
How can some of the more extreme ideas from Learnable Programming
make hot-swapping an even better experience?
How would Elm integrate with an IDE like LightTable that is already focused
on making the tools for Interactive Programming?
Both of these questions are more on the IDE and tooling side of things.
Elm provides a solid foundation for hot-swapping at the language level,
and I am excited to see how it can be used for truly Interactive Programming.