Commentary on Software and Anarchy

Software and Anarchy advocates for a liberatory approach to software development, where the use of dynamic programming environments can help small, decentralised communities to create and maintain their own software.

The book is split into three sections, with the first section — Liberatory Technology, pages 5 to 19 — being the focus of this essay.

A liberatory technology

The authors begin by introducing the concept of a ‘liberatory technology’, first described by Murray Bookchin in Towards A Liberatory Technology (1965). Software and Anarchy describes a liberatory technology as any technology that:

  • “provides an improvement in the work which can be done with some amount of effort and time”, and allows a community to “perform much more work independently” (p. 1)
  • is “thoroughly decentralised” (p. 5), and is “understandable, usable, and modifiable by a small community, allowing the community to support its use autonomously” (p. 6)
  • “can be adjusted [...] to fit its users’ applications and environments”, or, stated another way, “does not impede on how its users process things, instead complimenting their processes” (p. 5)

Liberation, in the sense of libertarian technics, encompasses both liberation from toil, where work can be performed more efficiently with fewer resources, and liberation from hierarchy, where work can be performed without centralised authorities or infrastructure.

The authors take the stance that a dynamic programming environment is such a technology, and that it can best “liberate the programmer and the act of programming” (p. 7). In software development, a programming language can be categorised as either static or dynamic, with dynamic languages being used to create programs for dynamic environments.

Programs written using a static programming language require a separate compilation stage before the program can be run. This compilation stage boils a program down to a sequence of instructions, stripping out labels and structures so that the program can run directly on a computer processor (an example of a ‘static environment’). Once a program has been compiled, it cannot be easily modified; instead, the original source code must be edited and recompiled in order to make a change. Examples of popular static programming languages are C, Zig, and Rust.

Programs written using a dynamic programming language will run directly, with labels and structures preserved while the program runs. Because of this, structured program state can be interrogated at runtime, and new functionality can be patched in without needing to restart the program. Dynamic programs can’t run directly on a computer processor; instead, a dynamic environment (also called a ‘language runtime’) acts as a translation layer, interpreting the program on-the-fly and executing code on the processor. The dynamic environment is itself implemented as a static program. Examples of popular dynamic programming languages are JavaScript, Python, and CommonLisp.

Put another way, static environments operate on opaque sequences of instructions, and dynamic environments operate on rich interconnected data structures.

The benefits of dynamic environments

The authors spare no expense in promoting the many benefits provided by dynamic environments over static environments, to the extent that the book can read at times like a propaganda pamphlet. Namely, the benefits are that dynamic programs:

  • are easier to develop
    “there is much less latency [when working on a dynamic system] than when compiling, running and debugging entire [static] programs” (p. 7) Programs can be updated live, with program state carrying over between changes. This can be much faster than recompiling and restarting a static program and repeatedly navigating back to the section being modified.
  • are easier to understand
    “a programmer can see their code running and inspect the data it uses instead of having to imagine both” (p. 8), and “can ask [...] what the result of evaluating some code is, and what the properties of some [objects] are” (p. 7). A limitation of static systems is that system state is often not easy to query or interpret, and programmers will often have to resort to simulating parts of the system in their head just to understand what’s going on. With a dynamic environment, the full system state can be viewed directly at any time.
  • are easier to extend and modify
    “a dynamic system can be trivially updated while still running, which is crucial for [...] servers and machine controllers” (p. 8), and can “[use] objects and types [it has] not been programmed to use” (p. 10). Objects in a dynamic system are often represented as pointers to heap-allocated key-value maps (sometimes called tables or dictionaries). Because of this uniform representation, objects of different types can be used interchangeably, with missing or unexpected fields handled gracefully. This sits in contrast to static data types, which are often stored as opaque sequences of bytes.

Despite these listed benefits, I believe that the authors’ case for dynamic environments is overstated, and that a compelling argument could be made that static environments offer some of the qualities of a liberatory technology.

A quick defence of static environments

This section aims to offer an explanation for the perplexing and continued popularity of static programming environments.

  • Static programs are small
    The compilation stage required by static environments presents a trade-off, creating very small executables at the cost of flattening program structures, freeing up memory that can now be used to tackle larger problems. As an example, a standard ‘hello world’ program in Rust is 434 KB (release mode, 1.87.0-nightly), whereas the same program in CommonLisp is around 30,000 KB (source, SBCL) due to it bundling the entire language runtime. The authors ask of static environments: “so [that] you can write code for a microcontroller?” (p. 16). I say: yeah, why not? We can reduce our dependence on centralised infrastructures by writing programs that run on cheap microcontrollers, shared over slow networks. Shouldn’t a liberatory system “[adjust] [...] to fit its users’ environments”? (p. 5)
  • Static programs are fast
    The authors claim that “high-level languages can frequently perform faster than static languages in symbolic processing” (p. 16), but the cited article (archived) compares a C++ program using reference-counted pointers to an OCaml program using an efficient garbage collection algorithm (furthermore, the OCaml garbage collector was itself written in C). This only reveals that dynamic environments tend to include large libraries of efficient algorithms. In the general case, static languages tend to be much faster at data processing than dynamic languages, with an article from 2021 (found here) comparing the speeds of similar word frequency counting programs across various languages (C++: 0.27 seconds; OCaml: 1.18 seconds). Shouldn’t a liberatory system “be efficient”? (p. 7)
  • Static environments are simple
    This is supported by the text, with the authors stating that “designing and implementing a dynamic system may be more difficult than with a static system”. This is because dynamic environments often handle the creation, manipulation, and garbage collection of complex data structures, whereas static environments often use just a handful of fixed-width registers or stacks, leaving programs responsible for their own data structures. Because of the inherent simplicity of static environments, it is easier for a small community to design, implement, and maintain their own environment. If the community later needs to use their programs on a less common architecture, they can re-implement their environment on the new architecture, instead of relying on the maintainers of a more complex environment to do this work for them. Shouldn’t a liberatory system be “thoroughly decentralised”? (p. 5)

There’s a lot to work with in here. If we read over the criteria for liberatory technologies once more, we can start to find small ways in which dynamic environments fall short of ideals, where broad assumptions have been made about the needs and wants of the communities that might benefit from them. With efficiency being traded for uniformity, and compactness being traded for runtime extensibility.

There’s an opportunity here to bring other techniques to the table; after all, dynamic environments don’t hold a monopoly on liberation. What if we do away with these assumptions? What if we take a different approach?

A different approach

How could the qualities of a static system be turned to the purposes of liberation, supporting the needs of small, autonomous communities? What could a liberatory static environment look like?

My answer to these questions is Bedrock, a minimal 8-bit computer system that approaches liberation from a different direction.

Introducing Bedrock

Bedrock is a static system: programs are written using a minimal assembly language, assembling down to a sequence of instructions that runs on a virtualised Bedrock processor. Like with a dynamic system, programs are interpreted inside a virtual environment instead of running directly on a computer processor. Unlike with a dynamic system, the environment requires very few moving parts, and can easily be reimplemented on whatever computer hardware is at hand — from laptops to cellphones to low-end microcontrollers.

Bedrock is human-scale: the core system can be implemented by a single programmer in the span of a day. All that is needed is a copy of the specification, a basic understanding of bits, bytes, pointers, and arrays, and a means to program the target hardware. As long as a copy of the specification is kept around, Bedrock programs can be revived on any hardware long into the future, even after the source code for all existing Bedrock implementations has been lost.

Empowering a community to implement Bedrock on their own hardware serves to flatten the traditional hierarchy between system maintainers and system users. System users no longer rely on the work of an external group of system maintainers to add features, fix issues, or port the system to new hardware; all of this work can now be carried out independently by the community.

Bedrock is also malleable: the system is small and can be modified to fit the specific needs of a community. The system interfaces with a range of different capabilities (such as keyboard input, file management, and network access), and more esoteric hardware can be connected to one of the four interface slots reserved for custom devices. If this is insufficient, the system can be modified more drastically, by swapping out individual instructions, whole device interfaces, or changing the fundamental data type in order to meet specific requirements. Programs written for Bedrock as specified won’t be compatible with such a heavily modified system, but this isn’t a big concern; the aim is for the system to fit the needs of a single community, not for all communities to conform to the needs of a single system.

Other modes of liberation

The authors focus on liberation from the toil of programming, but there are other modes of liberation to be considered.

Modern computers are complex and expensive machines. The second-hand laptop used to write this piece is 10 years old but still cost NZD$400 to purchase; it also draws around 5 watts of power, which is difficult to supply without either a large solar panel (in the daytime) or centralised power infrastructure. Centralised infrastructure was also necessary to manufacture the computer in the first place. A community would have to work to afford a computer before they can even begin to benefit from a particular programming environment.

What if, instead, we could build a computer from a handful of cheap components? What if we could power a computer from a pair of AA batteries, or from the heat of a candle? If we could design a programming environment that is small and efficient enough to run on such a computer, then communities will be free to design and construct their own hardware, running on free energy and free from centralised infrastructures. We could liberate the bedrock itself.

The bedrock-micro project (in-progress at the time of writing) is an example of this approach to computer hardware design. The design uses a memory chip, a voltage regulator, two logic chips, and a bottom-of-the-barrel microcontroller, all up costing NZD$15 and drawing just 13 milliwatts of power (between 2 and 5.5 volts). The whole circuit will fit comfortably on a board the size of a credit card, and will accept pretty much any type of battery as a power source. A pair of AA batteries will power it continuously for a month.

Our choice of environment determines the constraints that we work within. Bedrock is designed to use as few resources as it can get away with, and can run on hardware looking nothing like a computer. The system can be implemented on a game console or an ebook reader or as a block of JavaScript on a web page. If we change our assumptions, we can open up whole new horizons of possibilities.

A hybrid approach

We aren’t just limited to choosing between a fast static environment or a flexible dynamic environment. If we build a dynamic system on top of an interpreted static system, we can take the best elements of both.

A hybrid static-dynamic system is a dynamic environment implemented as a regular program on a minimal static environment. This makes the system exactly as easy to port as the underlying static environment — since the dynamic environment is just a program running on top, the system will run unmodified on any hardware for which the static environment has been implemented.

A hybrid system will be slower in the general case than a dedicated dynamic system, due to the extra static layer that sits between system and hardware, but the advantages provided by such an easily portable dynamic environment should be more than enough to justify this cost: programs will be easier to develop, easier to understand, easier to extend and modify, and will run on any hardware imaginable. For use-cases in which the dynamic system imposes too heavy a performance cost to be practical, programmers can choose to either replace the critical functions in their program with hand-optimised static code, or otherwise reimplement the entire program to run only on the underlying static system.

There’s still a lot of work to be done to see how this approach works out in practice.

Conclusion

The points made by the authors on the benefits of dynamic languages have influenced the direction of my work for the better. Dynamic languages are an enormous step forward for teaching beginners how to write useful programs, and to help experienced programmers to write programs with lots of moving parts. Many useful programs wouldn’t exist at all today if the only options were static languages like C and Rust.

I can’t help but feel frustrated, though, by the insistence throughout the book that dynamic languages are unilaterally good and static languages unilaterally bad. Languages are just tools, and each tool brings with it a list of advantages and disadvantages. Just because a tool is good for one task doesn’t mean that another tool can’t be good for something else; liberation isn’t a zero-sum game.