The current programming world view is very program-centric. In this write-up I propose a broader, system-centric perspective, which I belive in, and which I think is a better position if we are to really improve the world of computing and programming.
The Wikipedia entry for computer mentions ‘programs’ in the second sentence. We obsess over programs — how to create programs, build, run and debug programs, visualize programs, which language to choose to write our programs, what are their type systems and syntax, and so on.
On the surface there doesn’t seem to be anything wrong with this, but there’s a presumption that the programming world revolves around programs.
If we start by saying that we want to ‘write programs’, we have already locked ourselves into a specific frame of mind - that there is a program that must be written in full and then submitted to the system, to be built, integrated and then run.
But isn’t this jumping ahead a bit? Is a ‘program’ really the end goal?
My position is that we need to re-frame our goal using a system-centric point of view, which goes as follows:
- The system consists of the hardware and software machinery that we interact with. This includes hardware devices and some unnamed software entities or artifacts.
- The system exhibits behavior — we interact with it and it responds to inputs, produces outputs — images on the screen, sounds and so on.
- We modify the behavior. In other words:
- The system does not exhibit our desired behavior.
- We ‘do something’ (involving the manipulation the unnamed software entities), after which;
- The system now exhibit our desired behavior 🎉
This ‘doing something’ in the general sense is programming the system and such a system, naturally, is a programmable system! Programming itself is a type of interaction with the system and perhaps there shouldn’t be a clear boundary between programming the system and using it.
OK, that was a lot of words, all to take a giant step backwards, but now I can propose the goal:
The real goal is to design the form and nature of the software ‘entities’ — the ‘programmable substrate’ that we manipulate and compose when we program and use the system.
I’m intentionally treating this entirely as a design problem that deals with the invention of concepts and abstractions that we may call the substrate. The design space is wide open - there is tremendous freedom here to invent notions, concepts, tools, media and so on, carving up the ‘imaginary world of software programming’ in any way we choose. But we must carefully identify any concept we introduce.
Do we want to introduce files and file systems? What about compilers, databases, operating systems, executables and OS processes? All the the usual paraphernalia of computing is out of the window and we have a blank slate. What do we do with all this freedom in our newly found post-program (or perhaps, pre-program 😜) mindset?
The Program Idea
First, let’s bring back the program idea, but with the awareness that this is just one way to program the system (the distinction is between the verb programming and the noun program).
We say ‘there is something called a program’ and then proceed to describe what this concept is, how it is used, etc. This is the stuff we typically find in introductory materials for computers or the Wikipedia entry for ‘programs’. We have all internalized the ‘program idea’ but lets say a few things about it anyway.
The program is a document like construct created by a programmer — it has some structure presented via a primary visual representation through which the programmer views and manipulates it. A program contains the specification for an execution — this implies that it can be ‘run’ at some point, which produces the ‘running program’. Not to be confused with a ‘non-running program’, the running program is the original program plus some run time state attached to its various parts which changes as it runs. This introduces the idea of two phases — running and not running. It also defines this idea as a generalization — a program could have multiple running instances where the details are different.
OS Processes as Programs
In Unix (and Windows and Macs), the program idea manifests commonly as an OS process. If we accept this association, that an OS process is a kind of program, we can continue with more descriptive details. For instance, when a running program is stopped, all the run time state is immediately erased. For longer term state, a program may modify state outside itself while it is running, which means we need to define what external state looks like, or at least the coupling between the inside and the outside.
We also need some software tooling that is used to write and invoke a program — we operate on the program from the outside, using other programs such as editors, compilers, shells and GUIs.
Finally, we need some presentation and interaction concepts via which the running program interacts with us. Typically this is the ‘run time UI’ — a secondary visual representation of the same program — this one designed for the user. In some cases the programmer may also be the user, but the two distinct presentations are still employed. The program may also interact with other programs — this brings up questions of how they locate each other and what the nature of the interaction is.
The thing I’m trying to get at above is that there is the frame of the program — the common, unspoken assumptions that exist when we work within an idea. I want to note three things here:
There is a large set of choices and we are making here and we can decide to take a different path at any point. After all, these concepts are made up. For instance, we could choose a non plain text representation for all programs. Or we could choose a single presentation (instead of two separate presentations — primary and secondary, also known as the source code and the run time UI). We’d still have programs but they’d work differently.
It seems we can’t just introduce one standalone concept. Concepts exist in relation to others so we are forced to introduce a whole ensemble of interconnected concepts. Does this mean that if we introduce the ‘program concept’ into another world, we may have to pull in a lot of associated concepts as well? Possibly.. something to think about if design whole software worlds.
There’s a boundary that emerges, which defines what problems the program/OS process ideas solve and what they don’t solve. For instance if I want to say I want to define smaller programs that live within a larger program (a reasonable idea because any process should be decomposable into smaller processes), I’m on my own.
Considering how the mainstream operating systems work, we can also identify a whole array of design choices made that decide what is included and what is not, such as:
- Persistent system state outside all processes is held in the hierarchical filesystem as named blobs of bytes, explicitly managed by running processes.
- IPC and process-kernel-communication is implemented as API calls using C semantics.
- No isolation mechanism is provided by the system to separate a single process into different sub-parts.
- No mechanism is provided to save the running process state or rollback to a previous state.
- No mechanism is provided to replace part of a running process.
Whatever is not provided by the system may be invented by specific programs, ‘in user space’, but the core abstractions determine still what is common and easy.
Enough talking about ‘programs’, lets move on to more interesting questions:
Do we need to introduce the notion of a ‘program’ into our bag of software artifacts? Can we have a programmable system without the ‘program idea’?
The easy and obvious answer here is to consider any Smalltalk system as the whole system. Not as a program running within another system (i.e. not as an OS process within Unix, but as something that a system boots up into).
A Smalltalk system provides the concept of ‘objects’ and ‘messages’. ‘Programs’ (or files, for that matter) are nowhere to be found. The system is an ensemble of objects sending messages to each other. Each object is kind of like a mini-system — receiving messages and sending them — each has behavior. Some special objects called classes are used to compress behavioral definition of a large number of similar objects. Input devices, output devices and other UI paraphernalia are represented as objects. Messages represent events and interactions.
Programming this system involves sending messages to reconfigure the ensemble of objects, or creating some new objects and classes at the appropriate coordinates. It does not involve writing programs.
There is only one ‘phase’ as there is no point of compilation - the system is always live. With automatic persistence, when the system is turned off and then on again, the objects come back to their last state.
Other interesting aspects of this world: objects may have multiple presentations - all of which are available to anyone using the system, either as a user or a programmer.
There is no built-in verification system for inter-object messaging, but we could imagine layering on concepts that introduce some consistency checking for clusters of objects, or perhaps for each point-to-point ‘link’ between objects as they are formed.
The point isn’t to say that Smalltalk is a better system, but rather that it reveals a fundamentally different view of the system, and there may be other very different and more interesting views as well.
The System Perspective
Programs are just small slices of a larger system. Crafting a system by writing programs implies that we first slice the system into something called programs which have specific characteristics. There could be many other ways to slice the system — this is the space we should explore.
Some characteristics of the program-centric point of view contrasted with the system-centric one:
|The Program||The System|
|Is either running or stopped||Is always on|
|Is document-like - it doesn’t change by itself||Is interactive and living|
|Is created, built and deployed with tools that exist outside itself||Is programmed from within, using artifacts that are part of the system|
|Inspires the idea of ‘static verification’||Inspires the idea of continuous integration|
|Is transient and needs to persist state outside itself||Is persistent and encapsulates state|
|Leads to the idea of static binding (at compile time) vs. dynamic binding (at run time)||Encourages the perspective that new bindings are continually being formed on an ongoing basis|
Most of the slicing of the system is done before we even begin to program — it is entrenched in the concepts and artifacts provided by mainstream software: operating systems, files, applications, databases, compilers etc. From my perspective we need to rethink this entire model of decomposition. Just inventing a new version of an existing slice — such as a new textual programming language — isn’t going to cut it as we’re still operating in fundamentally the same model.
We need a different way to slice the system. To elaborate what I mean by that, below are some vague ideas. We are designing a new OS perhaps, if so, we are really redefining what it means to be an OS.
Axes of Freedom
While the Smalltalk model holds a large number of objects in a single ‘space’ - we could imagine a system where each object has internal space which holds a cluster of inner objects, and so on. This introduces a recursive aspect in the design.
Along a different direction, we can also consider replacing the write-then-run program idea (this is the ‘submit a paper’ model) with an incremental model where the programmer successively provides more detail about the desired behavior and a constraint solving system refines the behavior (this is the ‘have a conversation’ model).
Yet another approach could be to introduce managed time as a first class concept in the substrate — this implies all artifacts are versioned and their evolution is tracked. This means you can revert or move forward in any context — such as pausing or rolling back any span of any execution.
What about having a high level messaging model built into the OS? This eliminates the need for reinventing byte level representations of various types.
Or maybe we could start with running a VM everywhere and programming to this VM so things like distribution, isolation and splitting execution across machines is easier.
Could our system be a giant grid of interconnected cells with Excel like functions, some reasonable namespacing and automatic reactive updating? Input/output could be represented as special cells as well.
We think we have a lot of freedom when designing new programming languages but in fact the entire crop of mainstream programming languages are just emergent patterns of the mainstream systems. A different substrate would give rise to different patterns.
What would 'programming languages' look like if the system gave us something like reactive cells instead of 'files' and 'processes'? Syntax debates would be so different because we're working with cells and not free form plain text. Reactivity is part of the core features of the system so we may not need to imagine build systems.
There are just too many options to explore in this space — even the order in which we introduce concepts makes a difference. Perhaps we could look for the good ideas in the systems we know and see if we can distill the essence? The next steps are not really clear to me, which makes this exciting.
In the excellent Semprola paper, Oli Sharpe writes:
Another traditional choice is to view a program as an isolated mathematical or otherwise formal construct whose semantics is mostly determined at compile time in reference to itself and its imported libraries. And it makes a lot of sense that we’ve inherited this view given the history of programming, but today many “programs” are really just small parts of a greater, “living” network of programs and services that are each being updated at their own pace. Therefore there is no single moment of compilation and the semantics of one part in relation to the whole can be updated even if that part isn’t being changed.
This captures so well the perspective we adopt in the program-centric mindset. Another great write-up is Programming as interaction where Tomas Petricek writes:
In other words, programming language research should not study programs, but should instead study programming!
Tomas emphasizes looking the entire workflow and not looking at just the final, reduced product of ‘typing out a program’.
Instead of thinking about ‘writing programs’ we should think of ‘crafting systems’ as the primary goal. The system is a continuously evolving, forever live entity that we want to program using abstractions we might call ‘software substrate’. What ideas, concepts, abstractions belong in our core substrate? That is the question we need to ask.
In the beginner's mind there are many possibilities, in the expert's mind there are few. — Shunryu Suzuki