Building Bridges
Programming is neither an art nor a science, as I wrote in Orthodoxy, but we have an advantage over other crafts in that we can experiment cheaply and our material is our own creativity, like writers rather than sculptors. Where a sculptor working in wood can never escape the possibility of an unexpected knot or flaw in the grain, we’re able to tie our “material” directly to proven theory and to physical reality. Most of us won’t be working directly on either of those topics, though: computer engineers and theoretical computer scientists are rare, when compared to business focused software engineers.
No, we get to do our work in the messy middle. Think of a bridge: many computer scientists are working on building a firm foundation in indisputable mathematical truth on the left bank, while computer engineers and systems-level developers are laying brick on the right bank. Most software developers are working on the main span, furiously nailing bricks together and welding planks to plastic as fast as they can. We’re concerned with those hammers, nails, and welding torches and trying not to look down at the churning water below or paying much attention to the elegant towers rising on either bank.
Surely our numerical superiority has no negative implications for online technical discourse, right? Well…
I was already familiar with Haskell and category theory before Go arrived on the scene, and started my programming journey with BASIC and C in the 1990s. I understood the grounding that motivated Rob Pike to try to ignore “architecture astronauts” and revert to a “simpler” model of error handling and program construction. I also understood the rigor that he was sacrificing on the other bank by doing so. It felt strange to me, and through experimenting with the language grew to an aesthetic revulsion. It’s a bridge built by people ignoring foundations on one bank.
Similarly, Haskell itself (although an important language to learn) is almost never the tool that I reach for in my own projects. It’s simply too divorced from the real constraints of hardware and execution. You can write a beautifully abstracted piece of business logic that’s as perfect as a crystal, impossible to extend or evolve, or you can run afoul of a space leak and find your web service suddenly requiring more memory than a Google data center.
The “pragmatic” middle-of-the-river languages are even worse. I have had the opportunity to work with multiple companies at various stages of failure because the freedoms of Ruby and Python allowed them to construct systems that were literally intractable, where a total rewrite would’ve been the only possible path forward but was politically impossible.
In fact, I have come to believe that the “middle-of-the-river” mentality has been the biggest drag on the industry over the last fifty years. It neatly explains most of the things that people hate: architecture astronauts exist because they are trying to impose order on systems without firm grounding on either bank; agile is necessary because you aren’t building well-understood components; complication is nearly inevitable because it’s impossible to understand the system; and “cargo cult”/“guru” phenomena are encouraged and supported because there is no alternative without appeal to one or both banks.
The last point is particularly dangerous because the false solutions that gurus offer are surprisingly sticky. For example, my own cautionary article about simplicity often gets conflated with a particularly famous talk on the subject that I detest specifically because it is advocating a particular model of simplicity while also being actively hostile to the “left bank”—and failing to acknowledge the “right bank” at all. Words with existing (useful!) meanings get co-opted into an orthodoxy whose hegemony is extended by its adherents.
I think the path out of this “tar pit” lies in the acknowledgement and use of both foundations that our colleagues are building. Languages, tools, and methodologies that acknowledge computational reality while also aspiring to encode as many mathematical foundations as possible represent the future. For these purposes, I think languages like Rust, OCaml, and Swift show promise. TypeScript is also a practical choice, resting entirely on the “right bank” foundation cobbled together for JavaScript out of necessity because of its ubiquity. None are ideal: Rust is too low-level for many business applications, and has a learning curve close to El Capitan. OCaml’s packaging and building ecosystem seems like something from an alternate universe that forked sometime in the 1980s. Swift is too Apple. TypeScript sometimes feels like a Frankenstein that’s on the verge of falling apart (and its packaging and building ecosystem is Node). Still, all of them offer promising paths toward encoding more information in type systems, while also having predictable and tuneable performance characteristics.
Methodologies are more challenging to the individual practitioner. Even suboptimal languages can be written with skill and taste, but developing those requires a critical and broad approach that doesn’t come natural to all of us.1 As for the most immediately useful and non-obvious methodology, I think the concept of “functional core and imperative shell,” described in a Destroy all Software screencast as well as various blog posts and online discussions, fits the bill. In the “bridge and banks” model, you could think of this methodology as a way to use the left bank (theory) when you can and right bank (reality) when you must. The functional core, a graceful span founded on the left bank, reaches toward the center of the river as far as it can, modeling domain logic without recourse to concrete computing concepts and with little risk of logical error. The imperative shell, rising from utilitarian and brutalist foundations on the right, meets it in the middle. Bricks super-glued to balloons are minimized and used only where it’s genuinely unavoidable.
This understanding of the industry offers a clear path for improving our own capacities. While building from the middle offers only personality cults and slickly packaged “engineering frameworks” as anchor points, we have the opportunity to build from solid ground on both banks. Both foundations offer clear starting points, both left:
- “Why Functional Programming Matters” by John Hughes
- “Category Theory for Programmers” by Bartosz Milewski
- Types and Programming Languages2 by Benjamin Pierce
and right:
- Operating Systems: Three Easy Pieces by Remzi and Andrea Arpaci-Dusseau (or from Amazon2)
- The Elements of Computing Systems2 by Nisan and Schocken
- Understanding Software Dynamics2 by Sites
These resources build systematic understanding rather than providing quick fixes or easy answers, and all require significant effort, but they’ll improve your sense of agency in both your practice and your career as a whole. They also give you the tools that you need to identify and avoid the salesmen so intent on selling you training for their new brick-welding techniques.