The Almighty Atom
The linch pin of functional cores
Every app has humble beginnings, a few lines of code to kick things off. In functional reactive programming (FRP) that begins with an atom.
An atom is a state container, which is nothing novel given the growing number of contexts which use one. What Clojure calls an atom, Redux calls a store. I stole “atom” from Clojure because of how aptly it conveys the centrality of this one tiny mechanism required of any app which follows “the way.”
This atom…
// ./main.js
const $counter = $.atom(0);
…lives on this page:
<!-- ./index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Counter</title>
<script type="module" src="./main.js"></script>
<link rel="stylesheet" href="./main.css">
</head>
<body>
<span></span>
</body>
</html>
Think of an atom as a variable equipped with observability. You see, an ordinary variable stores state, too.
let counter = 0;
It’s just that when it changes nothing notices.
counter++; // now 1
But when the state inside an atom changes, it gets noticed by the parts of the program which depend upon on it. The following part, for example, subscribes (sub
is short for “subscribe”) to $counter
providing a callback which gets called whenever its value changes. The callback here updates the text of the span
element.
const el = document.querySelector("span");
$.sub($counter, function(n){ //react to the change
dom.text(el, n);
});
And since the callback is also called initially, before any change is made, we can anticipate the span
element will always reflect the current value of $counter
. So an atom is a special kind of variable which, when updated, triggers reactions. The reactions (via sub
) are usually devised to bring the UI into sync with the value held by the atom.
Calling swap
synchronously updates $counter
:
$.swap($counter, function(n){ //update the value
return n + 1;
});
The callback here receives the current value (n
) and provides a replacement value using the algorithm appropriate to the work at hand. Here, it adds 1 to $counter
, increasing it from 0, its initial value, to 1.
As it so happens, Atomic already has a function for that. So the same thing is accomplished more succinctly:
$.swap($counter, _.inc); //0 + 1 = 1
With each execution, the value of $counter
increments.
$.swap($counter, _.inc); //1 + 1 = 2
$.swap($counter, _.inc); //2 + 1 = 3
//etc.
What the app now lacks is a way for something (or someone) to interact with it, tell it what/when to do things.
Let’s remediate that by adding a button
:
..
<span></span>
<button id="inc">+</button>
..
Then subscribe to button clicks with an on
“click” callback. This kind of hook has long existed in jQuery, so nothing novel.
$.on(el, "click", "#inc", function(e){ //increment
e.preventDefault();
$.swap($counter, _.inc);
});
And that ties everything together into a simple app!
Let’s review. The UI (the DOM) and a single atom provide the minimal infrastructure needed for constructing a one-way data flow.
Data flows one way
One-way data flows are a key concept in functional reactive programming. Here the app reacts to changes in $counter
to update the UI, and to events in the UI (clicks) to update $counter
. It’s cyclical, a complete circuit.
Atom as data store:
//contain the initial state, 0, inside an atom
const $counter = $.atom(0);
Change output flows from the atom, effecting updates to DOM:
//whenever it changes update the `text` of the `span` element
const el = document.querySelector("span");
$.sub($counter, function(n){
dom.text(el, n);
});
Change input flows into the atom, in response to GUI events:
//when clicking the "+" button...
$.on(el, "click", "#inc", function(e){
e.preventDefault();
$.swap($counter, _.inc); //...increment the state
});
Take time to fully absorb this because that’s the lesson, and the foundation for building apps.
Let sink in how relatively little is needed. Just a few functions wire up UI updates (sub
, dom.text
), a means to interacting with it (on
, swap
), and—in the middle of it all—one tiny mechanism: the almight atom!
You’ve now been introduced to the “core” in functional core. What remains is examining what makes it functional. That’ll soon come into focus.
ML