M. Lanza

Start with Simulation

The headless core as command line

Recall the counter app we created.

It began with a single atom

const $counter = $.atom(0);

but quickly grew legs—or, at least, a head. That is, it displayed the current count in a span element and then momentarily later added a “+” button for incrementing it.

A graphical user interface (GUI) is typical of most apps. A user needs a way of seeing (outputs) and manipulating (inputs). In a web app, that’s on top of the DOM.

So, the counter app has an output (the current counter value), displayed in the span, and an input (accepting clicks), the button, necessary to manipulate it. It was minimal, yet complete.

And we began that way to understand inner workings (cores) and outer workings (shells, which include GUIs) go hand in hand. A core wrapped in a graphical user interface is the destination one bears in mind.

But your app, be it a counter or something more grand, will begin without the interface as a simple atom.

const $counter = $.atom(0);

An app without an interface is said to be “headless.” The trick to interactivity in this stage begins at the console as previously instructed, importing reg and, at minimum, registering the atom and functional core, which will initially be blank.

import * as c from "./counter.js"; //functional core
import {reg} from "./libs/cmd.js";
..
const $counter = $.atom(0);
reg({$counter, c});

This displays $counter in the log where you can expand it and see its contents, including its 0 value.

You see, even in the headless stage, you must keep in mind how the app will look and behave.

Having built a counter app, we know it provides a means of

  1. displaying the count (an output), and
  2. increasing it (an input).

From the command line enter:

$counter

This returns the $counter for inspection since reg exposed it globally. This was to allow you to interact with it.

Enter

_.deref($counter)

and

$.swap($counter, _.inc)

The first displays a 0 to the log, no display required. The second increases the counter to 1, no button required.

The counter is now 1! That will have been echoed to the console. Due to the monitor parameter, Atomic reports whenever a registered atom changes.

Press the up arrow in the console to display the same line again. Then hit enter.

Now it’s 2!

So although it’s not graphical, you have a command line interface. It provides the means to read (#1) and update (#2) state—everything required to interact with the app.

Let’s look closer at a command, the kind of function passed to swap. Internally, inc might look like this:

function inc(n){
  return n + 1
}

It receives the state of the app (n) and applies a fixed algorithm (n + 1) for transforming it to the next state. This function is nonconfigurable because it accepts no other parameters.

But what if it did? Let’s make a higher-order version which accepts an additional parameter.

function increments(x){ //one or more parameters
  return function inc(n){
    return n + x;
  }
}

Now, there’s fine-grained control for reconfiguring the incrementing.

  $.swap($counter, increments(1));
  $.swap($counter, increments(10));

It can be conditionally reconfigured from inside event callbacks:

$.on(el, "click", "#inc", function(e){
  e.preventDefault();
  $.swap($counter, increments(e.shiftKey ? 10 : 1));
});

The swappable function must minimally accept the app state. But it may also take additional configuration parameters.

Your concern in the headless stage is fleshing out the core by writing the commands you anticipate a user needs to get things done. You quarantine the pure functions you write to the core (counter.js). Everything else, including issuing commands, is done in the shell (main.js) and/or from the browser console. The shell actuates what the core simulates. It renders the GUI onto the DOM and responds to user interactions with it.

By adding your commands to that module…

// ./counter.js
export function increments(x) {
  return function(n){
    return n + x;
  }
}

…which is imported in your shell, the commands will be readily available for use there…

// ./main.js
import _ from "./libs/atomic_/core.js";
import $ from "./libs/atomic_/shell.js";
import dom from "./libs/atomic_/dom.js";
import * as c from "./counter.js"; //functional core
import {reg} from "./libs/cmd.js";
const $counter = $.atom(0);
reg({$counter, c});
//an arbitrary user story you're working...
$.swap($counter, _.inc); //1
$.swap($counter, c.increments(10)); //11
$.swap($counter, _.dec); //10

…or from the command line:

// browser console:
$.swap($counter, c.increments(10));

Keep at it until the atom tells a complete story. Then, retrofit it with a head.

But counter apps, as they go, are trivial. To better grasp what’s been illustrated, let’s kick things up a notch—build something a bit less trivial.

ML