When I first started programming as a high school student taking AP computer science, each program was a struggle. I had to learn how to use an IDE, how to avoid syntax errors, how to decode the compiler output. The complexity of the C++ development environment was not a help here. The feeling was that each little task was an enormous effort.

But at a certain point, things began to click, and the feeling reversed. It seemed that, with almost no effort at all, but just a bit of thought and imagination, I could make the computer do anything. I remember one assignment very clearly, which was the Towers of Hanoi: I had the insight that the problem could be solved recursively, which is of course the intention of the assignment. So I wrote out the algorithm - a few lines of code following exactly how one would explain the idea in English - and then wrote a short, straightforward visualization routine. All of a sudden - from what felt like almost no work at all - the computer was showing me the hundreds of moves needed to move a stack of disks from one peg to the other.

// Towers of Hanoi, in Javascript
pegs = [0,0,0,0,0,0,0]
move_pegs(n, from, to) {
  if(n < 0) return
  let other = 3 - from - to
  move_pegs(n-1, from, other)
  pegs[n] = to
  console.log("Move peg " + n + " from " + from + " to " + to)
  print_pegs()
  move(n, from, to)
  move_pegs(n-1, other, to)
}
move_pegs(6, 0, 2)

What I didn’t realize at the time was that there were particular reasons why, in this assignment, everything felt so straightforward. For one thing, the curriculum had of course been designed to avoid any problems that I would not know how to solve, or that were not amenable to computer solution - I certainly would have had a worse time with the traveling salesman problem. But also, by using C++ I was actually benefiting from decades of research and experimentation in programming languages, which had led to a deceptively simple, enormously powerful and elegant system for decomposing this kind of input-output problem.

I’m talking about imperative programming, also called structured programming - the paradigm for programming languages which has been dominant since ALGOL in the 1960s. Nowadays the imperative paradigm is often denigrated: it has been used as a whipping-boy by proponents of newer paradigms like object-oriented and functional programming. This discourse can obscure how great an advance imperative programming was—it is no coincidence that the basic machinery of loops, conditionals, stacked function-calls, and lexically scoped variables have become ubiquitous, while older technologies like GOTO, static function calls, and dynamically scoped variables have tended to die out.

The basic insight of imperative programming is that the state of a running program is enormously complex: far more complex than a person can keep track of. The way that E.W. Dijkstra and others came up with to manage this complexity was to divide the state of a program up along the dimension of time. The principle unit of division is the function: a function takes its relevant pieces of state as input, and returns its relevant results as output. The internal transformations of the function, however complex, can be summarized as input -> output within the timeline of its parent function. And even the function is further decomposed as structured control-flow operators which meaningfully divide the timeline of the running program - while which loops through the same instructions until a condition is met, if which executes exactly one of two branches, and so on.

Unfortunately this model has its limits. For me, I crashed into the limits of what I’d learned in AP computer science when I first tried to write a GUI application. For this effort I tried to use C++ with the Win32 API—a poor choice to be sure. I was brought back to the earlier days of my programming education, when every little change was an agonizing effort. Later I learned some easier ways to build UIs - like using perl with GTK, and then HTML/CSS/Javascript, the latter of which has been the basis of my career. However, I never regained the heady sense of power that I felt when I was first working through simple console applications in C++.

There is a profound reason why the GUI so frustrated the instincts I’d picked up learning imperative programming: the two are fundamentally incompatible. Imperative programming assumes that the operation of the program can be broken down along the dimension of time, so that while a particular function is running, the state outside of that function can be ignored. But in the context of a GUI this assumption does not hold. The user expects to have many choices available of what to do next, and many relevant pieces of information displayed, at one time.

The earliest response to this problem was a minor modification of the imperative scheme - the whole UI state was held in a global state object, and the UI itself was a loop of responding to events by updating the UI. A typical UI of this type will show a screenful of information, with a listing of key shortcuts in a header or footer. These key shortcuts will either have an immediate effect, or will take over the interaction with requests for more information. This approach kept the basic spirit of imperative programming in between the event and the output, but it badly degraded the imperative style isolation of state - anything relevant to the UI became a piece of shared, global state. It also did not produce very good UIs - it was hard to develop reusable “widgets” with their own state and behavior using this scheme, so all the interactions tended to be either stateless (acting immediately) or modal (taking over the UI).

This global-state approach was an attempt to fit UI into the imperative paradigm; but the next approach was to overthrow the imperative paradigm to support the needs of UI. The object-oriented paradigm was developed alongside the modern GUI, and I am convinced that this is no coincidence: object-oriented programming is an encoding of the logic of graphical user interaction into the language of programming. In object oriented programming, a program is distributed in space rather than time. It is a collaboration of stateful objects, any one of which might be called at any time. Each object has its own state and history. This maps very naturally on to the GUI with its stateful, interacting widgets. A menu, for example, becomes an object - it has its “local” state, like whether it is open or closed, and then it sends bits of state in messages to other objects - like when a user clicks a menu entry.

Unfortunately, this “spatial” as opposed to “temporal” division of a program is more an aid to organization and intuition than a real tool for managing the complexity of application state. In general, the state of an object depends on all the messages it has received, which depends on the history of all the other objects in the system, which in turn depend on each other. At best the OO style allows a division of state into private, local pieces that should be irrelevant to outside of the object; but there is not really any syntactic guarantee that private state won’t leak out one way or another, and anyway when there are real interactions required, the programmer has to reason about the whole potential state of the non-private parts of the system.

I have been writing GUIs for some years now, and I’ve seen many UI frameworks come and go, but besides the introduction of arbitrary divisions and hierarchies, all of them seem to me to fall in one camp or the other—global state with an event loop, such as many uses of React, curses, game UIs, and so on; or more commonly stateful “widgets” and messages (which might be sent on every variable update, following an observer pattern - this is no fundamental difference).

I’ve also written a number of UI frameworks. Since about 2010, I’ve written any number of prototypes exploring different ways of building a UI. I think I can understand how overly complicated frameworks like Angular come about—I have been in a similar situation, building one feature on top of another, complicating things to enable each problem that comes up, until I’ve added enough feature that the framework recapitulates one or the other of these basic solutions, and elated I feel that it is complete—only to realize at length that it is not an improvement.

After long labors, I think I have reached a level of clarity about the problem, summarized in my arguments above. And alongside that clarity I was able to pare my frameworks down to something very simple. The basic problem with an imperative approach to GUI is this: a GUI should present multiple stateful views to the user at once, and allow the user to interact with each of them as they see fit. The “temporal” division of the imperative program must be supplemented by a “spatial” division which allows parallel execution.

In Imperative.js, rather than throw over the whole imperative structure in favor of “objects” with no real structure, the imperative structure is conservatively extended with a parallel operator. Each imperative thread is considered as a generator of an output type o, typically an HTML fragment. Two imperative functions can be run side-by-side, producing an output type [o1, o2]. The first of these two functions to return will provide the return value of the combined function.

The h combinator in Imperative.js combines this parallel operation with the construction of an HTML DOM, so that h('div', [left(), right()]) will run left and right in parallel, output their outputs as the first and second children of a div, and return the value of whichever one is first to complete and return a value. This “spatial” combination can be combined at any level with the “temporal” combinations of structured programming: one can have a while loop inside a div inside an if statement inside a span.

Imperative.js is the outcome of a long series of experiments and prototypes; in many ways it is a javascript reimplementation of Sneath Lane, a Haskell library I released in 2015. The direction of this experimentation has been toward greater and greater simplicity, so that the core of Imperative.js is just the h function, implemented in about 50 lines of javascript. Of course, to support the full range of use-cases for a modern GUI, more features are needed; there are already several more-specialized modules for Imperative.js, and I am continuing to apply the paradigm to more UI problems.

Nonetheless, the reason that I am so pleased with Imperative.js is that this tiny, simple extension of the imperative paradigm actually allows, if not all the bells and whistles of a modern GUI, at least a very reasonable subset of them; and it retains the atomicity, simplicity, and universal applicability of the original structured programming style. After some 14 years, it brings back some of that old sense of infinite possibilities at one’s fingertips.