Austin Dumm


The Dream of Homogeneity
Austin Dumm
2024-02-25

One of my favorite software analogies came from Alan Kay’s talk, The Computer Revolution Hasn’t Happened Yet. I’ll rephrase as a parable:

A builder goes to an architect asking for blueprints for a doghouse. The architect responds with the classic doghouse design: 6 pieces of wood, nailed together, about 3 feet tall. The builder later returns complaining that the design worked as a doghouse, but collapsed when built 30 times larger into a cathedral. The architect explains that scaling the design up won’t work. At a certain size, the weight of the wood (growing by a cube rule) will outpace the strength of the wood (growing by a square rule) and it will collapse.

Kay argues that the same applies for software: at a certain scale the simple solutions fall apart under their own weight. For physical structure, Kay references the study of architecture and techniques that maximize physical strength with respect to weight. For software, he points to Object-Oriented Programming as a technique that can help solutions scale large in a similar way: by reducing the overhead needed to integrate with and understand the implementation of other code components. This is analogous to decreasing the weight.

Though, there is a flip-side to this that Kay overlooks. I’ll extend the parable:

To solve the problem of the collapsing doghouse, the architect instead gives the builder designs for a massive cathedral. The builder returns later complaining that the cathedral design worked but when built 30 times smaller for a matching doghouse, a light breeze caused the walls and windows to crumble and it fell over.

The architect explains that scaling down won’t work either as building large structures requires using materials that are strong when compared to their weight, but scaling down makes them so thin as to be brittle. The truth of the matter is, different architectural techniques are needed at different scales, in both directions.

The modified parable does not form an argument for sophisticated architecture and efficient structure, but rather it forms an argument against homogeneity.

This closely parallels the ongoing debate between between Data-Oriented (DO) and Object-Oriented (OO) styles of programming. DO, focusing on clear delineation between data and code against OO, with its focus on objects which contain both data and code. The first aims to model data that accurately and precisely maps to the problem to increase code clarity and data processing efficiency. The second blurs the line between data and computation behind abstractions and interfaces, aiming to decrease the complexity and cognitive load required to integrate with software. In my experience, the core difference between these two stems from the scale at which they approach software development yet both often hold an overzealous want to use their approach at all scales. These match the actions of the builder in the parable: start from one extreme in scale, and keep it homogenous no matter what.

DO starts from the “doghouse” end of the spectrum with a focus on the conceptually low-level. DO defines data structures which map well to the problem and are as efficient as needed, and then writes clear code to operate directly on the data. Code which depends on the literal structure of data in memory, at the small scale, can more easily remain clear, focused, and efficient. Writing a doubly-linked list in a low-level language is a great example of this. Each function and operation remains concise and clear specifically because the code is so tightly linked to the exact structures in memory.

Kay is correct in saying that, like the doghouse, scaling this approach up means that its weight might start to outpace its strength. The more code that is added, the more depended upon the exact data structures used will be. This means even code whose responsibilities are conceptually far away from the data itself likely includes at minimum some implicit knowledge of the data structures. This, with large teams and large codebases, will slow down development and increase the cognitive load required remembering all of the data specifics. Further, the more code that maintains some knowledge or dependence on specific data structures, the harder and more risky it is to change that underlying data structure. We can’t take the DO that works at the small scale and simply scale it up to keep a homogenous structure. Eventually, it will collapse.

OO starts from the “cathedral” side of things. When programming at a conceptual “large scale”, implementations hidden behind abstractions and APIs can ease integration efforts. The focus on interface/APIs means that no object needs to know the internal structure (both data and code) of any other object. With this approach, developers can use functionality without having to be intimately aware of implementation details. Similarly, implementation details of one object can be changed completely and, as long as the same API is supported, no other object needs to care. This seemingly solves the problems DO has at larger scales where changing data structure and implementation risks large-scale impact.

Like the cathedral, however, this approach becomes brittle as it scales down. As OO is used to create smaller and simpler objects, eventually we’ll end up dealing exclusively with interfaces and never with real implementations. This has an effect of decontextualizing code as it is very difficult to tell exactly what other implementations an object depends on at runtime; every dependency is hidden behind a API and it’s difficult to understand the context of code when every implementation is abstracted away. It’s also difficult to optimize when dealing exclusively with interfaces as optimizations often take advantage of implementation specifics. This is not even to mention the efficiency losses due to cache misses and indirection caused by overuse of small, dynamically-dispatched objects. Finally if OO is intended to maximize strength while minimizing weight, small objects are generally the area where weight is the least anyways. What is the point of decreasing overhead and complexity in integrating with components that were already small enough to be easily understood?

These issues are particularly difficult to express with OO because they are issues that arise at the scale usually used for teaching examples: the small scale. OO becomes more valuable and necessary the more complexity and implementation can be hidden behind APIs. If I am wrapping the functionality of a bank account management program behind an API of commands that do certain bank-related actions, any callers of that API can easily and happily remain ignorant of underlying implementations of the bank system. If I am wrapping a linked list behind an OO API, there’s a good chance my calling code is still relying on the particular behavior and characteristics of the raw linked list data structure itself. Encapsulation, polymorphism, and runtime dispatch don’t help if I only ever need specifically a linked list. But which of these examples is easier to teach? I’d say the linked list is easier because it’s so much smaller. It is not a surprise that so much OO code is applied at far too low a level because the simple/low-level/“linked-list” examples are the easier ones to teach. No one wants to explain an entire banking system to students just to demonstrate the value of OO.

This teaching of OO examples at the smallest scale extends to Smalltalk, Kay, and the lecture linked above. I agree with much of his perspective on OO at large scale. However, he mentions that one of the limitations of Unix was its multitasking overhead of 2kb per process making it “very difficult in Unix to let… a process just be the number ‘three’.” The issue here is less a failure in Unix processes, more an overzealous demand for homogeneity. Just because an OO cathedral works does not mean it will continue to be productive and beneficial scaled down to the size of a few bits. Is writing code that operates on numbers that difficult? Are math operations so complex that they should be implemented behind an interface, tied together with the specific value 3? I see this want to make everything an object at every scale as a misapplied attempt at homogeneity that gets in the way at such a small scale.

The key here, then, is knowing at what scales to apply DO and what scales to apply OO techniques. The unfortunate truth, leaving this article without much in the way of a prescription, is that the line between DO and OO sits at a different scale for every problem and every project. Video games can need to run so efficiently that more code is likely to sit near DO in order to optimize as much as possible. Business applications often need to minimize the impact of rapidly changing business requirements and long-term maintenance windows placing more code on the OO side such that objects and systems can be more easily swapped out or reimplemented. Picking or arguing for a universally “best” paradigm inevitably will break down at one scale or another. My suggestion is that it is more important to know the problems and scales that need addressing and use that information to decide on paradigm or style.

Looping back to Kay’s talk, he acknowledges some of this difference in scale when discussing OO concepts. He references the architecture of living organisms and cells, with cells and their function being a chief inspiration for his Smalltalk language and the OO techniques it developed. Within the cytoplasm in cells, he describes that proteins move incomprehensibly fast with respect to the size of the cell which makes the chemistry occurring within efficient and conducive to life. On a larger scale, direct chemical reaction within cytoplasm would be chaotic and disorganized so cells remain small and form tissue, or collections of cells. Communication within tissues occurs via cells excreting proteins and letting any neighbors notice them as they wish. On the largest scale proteins move far too slow and instead, the nervous system communicates over long distances rapidly with electrical signals being sent directly to specific areas. Life does not attempt homogenous solutions at these different scales.

If Smalltalk and OO is inspired by the communication happening between cells in tissue, we should realize that cells are already “high-level” with respect to the chemistry and complexity occurring within. Let DO and direct data manipulation be analogous to the internals of cells and let our software objects and architecture step in when larger scale is needed. Life is built by embracing these different requirements at different scales. Software would do well to realize the same.