andydataguy
< BACK TO FIELD NOTES
RANT . 12 JUN 2026
HOT TAKEPART 1 OF 3 . THE COMPILER TRILOGY

Part One: Objects Are a Position, Not a Religion

You are not rejecting OOP. You are rejecting a fork of it that stole the name. The synthesis is older than the debate, and every trail leads to the same summit.

12 JUN 202619 MIN READBY ANDY HOUSTON
[ HOT TAKE / SHOCKWAVE ]
#paradigms #ecs

I. The discomfort is the signal

Most arguments about programming paradigms are first-level thinking. "OOP is good because encapsulation." "FP is good because purity." These statements are not wrong, exactly. They are the kind of statements that are true enough to repeat and shallow enough to be useless, like telling an investor that buying quality companies is a good idea. Sure. At what price? Under what conditions? Holding what else?

Second-level thinking asks a different question: not "which paradigm is good" but "what does each paradigm actually buy me, what does it cost, and what is everyone else doing that I can quietly do better than?" The interesting opportunities, in markets and in architecture, live in the gap between what's true and what's commonly believed.

Here is what's commonly believed: object-oriented programming is the serious, professional default, the thing real engineers use for real systems, and functional programming is either an academic indulgence or a niche tool for data pipelines. Senior engineers will pull you aside and say, in a tone usually reserved for warning people away from day trading, "be careful, you still need OOP."

Here is what's true: the seniors are right, but not about the thing they think they're right about. And the discomfort you feel with class-heavy OOP is not a gap in your education. It is, in most cases, a correctly functioning instinct that has not yet been given its vocabulary.

A shallow parts drawer lifted onto a workbench, its compartments sorted by what each part does. Pinned on the wall directly behind it hangs a branching taxonomy chart drawn to classify the same parts, its branches struck through and crossed out by someone who gave up on it.
The chart that tried to classify the parts got crossed out. The drawer that sorted them by what they do still works.

One caveat before we start: paradigm arguments are like asset-class arguments. Nobody serious says "stocks are better than bonds," full stop. They say "given this client, this horizon, these liabilities, here's the mix." Everything below should be read in that spirit. We're constructing a portfolio, not joining a church.

Andy at a desk in deep navy void weighing two schools of thought on a literal scale: a rigid class-tree paperweight and a flowing message-mesh. Panels show the crowd's belief, what's true, and the gap as the opportunity.
Andy at a desk in deep navy void weighing two schools of thought on a literal scale: a rigid class-tree paperweight and a flowing message-mesh. Panels show the crowd's belief, what's true, and the gap as the opportunity.

II. The term "OOP" was hijacked, and nobody held a funeral

To have this argument at all you need to know that "object-oriented programming" refers to two different things, and the man who coined the term endorsed only one of them.

What Alan Kay actually meant

Alan Kay invented the phrase. His formulation, which he repeated for decades with increasing exasperation, was that OOP means three things: messaging, local retention and protection of state, and extreme late binding of all things. Objects, in Kay's conception, were supposed to be like biological cells or like servers on a network: independent units that hide their internals completely and communicate only by sending messages. He said, in so many words, that he regretted calling it "object-oriented" because everyone fixated on the objects, when "the big idea is messaging."

Classes are not in that list. Inheritance is not in that list. Taxonomies of AbstractBaseHandlerFactory extending BaseHandlerFactory extending HandlerFactoryImpl are very much not in that list.

The bait-and-switch, and the punchline for agent builders

What happened next is one of the great bait-and-switches in the history of the field. C++ and then Java took the word "object-oriented," kept the syntax of classes, and discarded the messaging philosophy almost entirely. Mainstream OOP became class-oriented programming: model your domain as a type hierarchy, distribute your mutable state across instances, and bind behavior to data at compile time through inheritance. This is the OOP you learned, the OOP of enterprise Java, the OOP of the God class. It won the marketing war so thoroughly that Kay's version barely registers as OOP at all anymore.

Kay's original vision, the one that lost, looks almost exactly like a modern agent system. Independent encapsulated units. Total state hiding. Communication exclusively through messages. Late binding of everything, because you don't know at design time what the other party will say. An agent harness, with its agents holding private context and exchanging messages through defined channels, is closer to what Kay meant by "object-oriented" than 95 percent of the Java ever written. You are not rejecting OOP. You are rejecting a fork of it that stole the name.

You are not rejecting OOP. You are rejecting a fork of it that stole the name.

PropertyKay's OOPClass-Oriented Fork
Core mechanismMessaging between independent unitsInheritance hierarchies binding behavior to data
State modelCompletely hidden, per-objectDistributed across instances, shared via inheritance
Binding timeExtreme late binding of all thingsCompile-time through class hierarchy
Kay's three OOP properties versus the class-oriented fork

III. Composition over inheritance: the call is coming from inside the house

The strongest evidence against inheritance-heavy design comes from the most canonical OOP text ever published, not from FP partisans.

The canon's own verdict

The Gang of Four's Design Patterns (1994), the book that sits on the shelf of every engineer who lauds OOP at you, states as one of its two foundational principles: "Favor object composition over class inheritance." That's not a hostile reading. It's on the early pages, stated flat. And if you actually read the patterns in that book instead of just citing it: Strategy, Decorator, Composite, Observer, Bridge. Half the catalog consists of elaborate machinery for avoiding inheritance. The most celebrated OOP book ever written is substantially a survival manual for escaping OOP's signature feature.

Why did they write that? Because by 1994, people who had spent a decade building large class hierarchies had already learned what you learned more cheaply by intuition. Inheritance is the strongest coupling relationship a language can express. A subclass is welded to its parent's implementation, not just its interface. Change the base class and you ripple through every descendant, including the ones written by the guy who left two years ago, including the ones that overrode a method you didn't expect anyone to override. The hierarchy that felt like elegant taxonomy on day one becomes load-bearing concrete by year three. This is the fragile base class problem, and it has been documented, named, and lamented for thirty years by the very community that keeps teaching inheritance in week two of every CS curriculum.

The God class, the terminal stage

The God class is the terminal stage of this disease. It begins reasonably: a Player class, a Vehicle class, an Agent class. Then requirements arrive, as they do, orthogonally to your taxonomy. Some vehicles fly. Some players are AI-controlled. Some agents need persistence and some don't. A tree can only branch along one dimension at a time, and reality is not a tree. Reality is a matrix. So you start pushing shared behavior up the hierarchy, because that's where shared things go, and the base class accumulates, and accumulates, and ten years later there's a class with 4,000 lines and 60 instance variables that everything inherits from and nobody understands, and modifying it requires a meeting. Your brain looked at this pattern early and said "no." Your brain was right. The GoF agreed with your brain in 1994. The seniors citing the GoF at you have, in many cases, not recently read the GoF.

A base class swollen to four thousand lines and sixty variables, with five requirement chips arrowed into it from different directions: flies, electric, AI-controlled, persists, shopfront. Beside it the same five sit loose as parts that attach in any combination.
Some vehicles fly. Some players are AI-controlled. Some agents persist. Each of those arrives on its own axis, and the base class absorbs every one of them.

A tree can only branch along one dimension at a time, and reality is not a tree.

Composition's opposite bet

Composition takes the opposite bet. Instead of asking "what is this thing," which forces you to pick one axis of classification and marry it, you ask "what does this thing have, and what can it do." Capabilities become parts. Parts combine freely. The matrix of real-world requirements gets represented as a matrix instead of being crammed into a tree. None of this is new and none of it is FP propaganda. It is the orthodox conclusion of OOP's own elders. The industry just has a forty-year habit of ignoring its own findings, which, if you've worked in finance, will not surprise you. Everyone knows the literature says active managers underperform after fees. The literature has minimal effect on behavior. Same energy.

IV. ECS: composition taken to its logical conclusion, with receipts

Entity-Component-System is what happens when you take "favor composition over inheritance" and refuse to stop early. The architecture has three parts, and the discipline is in how little each part is allowed to be.

The three parts, kept deliberately small

An entity is an ID. Not an object with an ID. An ID. A number. It has no behavior, no data, no methods, no opinions. A component is pure data attached to an entity: a Position, a Velocity, an Inventory, a PowerConsumer. No methods. Plain old data. A system is a function that runs over every entity possessing a particular combination of components: the movement system grabs everything with Position and Velocity and advances it; the power system grabs everything with PowerConsumer and drains the grid. Entities are rows, components are columns, systems are queries. A game tick is a batch job. ECS is your home paradigm wearing a different jersey.

Entities are rows, components are columns, systems are queries.

Two independent reasons it wins

There are two independent reasons ECS wins in large simulations.

The first is the expressiveness argument, which is the composition argument from the previous section. When an entity is just a bag of components, the taxonomy problem evaporates. A flying electric vehicle that's also a shop? Attach Flying, Electric, Vehicle, Shopfront. No diamond inheritance, no refactoring the hierarchy, no meeting. New entity types become data, not code.

The second is the mechanical sympathy argument, and this one is about silicon, not philosophy. Class-based OOP scatters objects across the heap, each one a grab-bag of hot and cold fields, accessed through pointers, processed through virtual dispatch. Your CPU hates every part of that sentence. Cache lines get filled with fields you don't need; the prefetcher can't predict pointer chases; the branch predictor chokes on virtual calls. ECS stores each component type in contiguous arrays, so a system ploughing through 200,000 positions is doing exactly the linear, predictable, SIMD-friendly memory access that modern hardware was built to inhale. The performance difference is not 10 percent. On the right workload it's an order of magnitude. This is the same reason columnar beats row-oriented for analytics, the same reason vectorized beats iterative in your pipelines, the same reason your GPU work felt familiar. Data-oriented design is the unifying idea; ECS is its game-engine expression.

The receipts, graded

Now, the games, and here I'll be more careful than the average Hacker News commenter. Your instinct that nobody is "raw-dogging OOP" through a million-entity factory simulation is correct, but the evidence comes in grades. Cities: Skylines 2 is the clean documented case: it is built on Unity DOTS, whose centerpiece is an explicit, formally documented ECS, and the developers have publicly discussed leaning on it for the core simulation precisely because the first game's traditional MonoBehaviour approach hit a wall. Factorio is closed-source; what's publicly known from the developer blogs is a relentlessly data-oriented C++ engine, prototype-driven entity definitions, flat arrays and tight loops, which is the ECS mindset without a confirmed textbook ECS implementation. Dyson Sphere Program is Unity, component-composed and aggressively data-oriented in its belt and logistics code per the modding community's decompilations, again hybrid rather than pure. So the defensible sentence, the one you can say to a principal engineer without getting picked apart, is: large-scale simulations converge on data-oriented, composition-heavy architectures because deep inheritance hierarchies fail at both expressiveness and throughput, and where we can see the receipts (Unity DOTS, and open ECS libraries like EnTT and Flecs), that's exactly what's there. Hedged correctly, the claim is stronger, not weaker.

Three million-entity simulations graded by what their engines verifiably run. Cities: Skylines 2 takes a full mark for a documented entity-component-system. Factorio and Dyson Sphere Program take half marks for being data-oriented without a confirmed one.
Cities: Skylines 2 is the documented case. Factorio and Dyson Sphere Program are data-oriented and composition-heavy, which is a different and weaker claim.

V. The steelman: where objects earn their keep

Let's take the senior engineers seriously, because hidden inside their vague warning is a precise position, and the precise position is correct.

When a thoughtful senior says "be careful, you still need OOP," they are usually not defending inheritance taxonomies. Nobody who has been doing this for twenty years defends the God class; they've all been mauled by one. What they're defending, mostly without articulating it, is this: some things in a real system genuinely are stateful resources with lifecycles, and an object is the cleanest container civilization has invented for a stateful resource with a lifecycle.

Stateful resources with lifecycles

Think about what your harness actually touches. Database connection pools. A Redis client. A headless browser. A Docker sandbox running untrusted agent-generated code. A websocket to a model provider. A long-running agent session with accumulated context. Every one of these has the same shape: it must be initialized (auth, config, handshake), it holds genuine internal state that is not incidental but essential (the connection IS state), and it must be torn down correctly or you leak processes, file handles, sockets, and money. The C++ world calls the disciplined version of this RAII: tie the resource's life to the object's life, and cleanup becomes structurally guaranteed instead of something every call site has to remember. Python's context managers are the same idea in pajamas. with Sandbox(root) as sb: and the orphan-process problem is handled by construction, not by vigilance.

A heavy gas cylinder chained upright on a bench with its valve and regulator fitted and a tag wired to the neck. Behind it a caged rack holds spent cylinders lying on their sides, valve caps back on and tags punched through.
A connection is not modelled state. The connection is the state, and it has to be torn down or you leak processes, handles and money.

Could you model all this functionally? In principle, sure. Thread the state explicitly, or wrap it in effect monads, or push it into a runtime the way Haskell does. In a mainstream language, on a mixed-skill team, what you actually produce by doing that is a hand-rolled object system with worse ergonomics and no IDE support, built by someone who will leave, maintained by someone who will curse them. The senior engineers have watched this movie.

The second reason purists lose

There's a second, less philosophical reason, and pretending it doesn't exist is how purists lose: the ecosystem is object-shaped. Pydantic models are classes, and they're classes for good reasons: validators, defaults, schema generation, autocomplete. PydanticAI hands you an Agent class. LangGraph hands you graph and node abstractions with class-flavored APIs. Every SDK for every vector database, observability platform, and cloud service hands you a client object. You can rage against this or you can notice that client. followed by autocomplete is a genuinely good discovery interface for a teammate who joined Tuesday, and that an API designed as forty free-floating functions is not. Fighting the ecosystem's grain costs you integration speed and onboarding speed and buys you ideological purity, which compounds at zero percent.

The object-shaped ecosystem: annotated contact sheet of client objects (Pydantic model card, Agent class card, vector-DB client card) with margin notes about autocomplete as a discovery interface.
The object-shaped ecosystem: annotated contact sheet of client objects (Pydantic model card, Agent class card, vector-DB client card) with margin notes about autocomplete as a discovery interface.

So the scorecard reads like this. Inheritance as a modeling strategy: discredited by its own inventors' literature, avoid except for shallow, stable, framework-mandated cases. Objects as containers for stateful resources with lifecycles: genuinely the right tool, use them without guilt. The trick, and it is the entire trick, is that these two things got sold under one brand name, and the brand confusion is what generates the whole tedious debate. You don't need a side. You need a sorting rule. The next section is the sorting rule.

Use caseVerdict
Inheritance as modelingDiscredited by OOP's own canonical literature; avoid except for shallow, stable, framework-mandated cases
Objects as resource containersGenuinely the right tool for stateful resources with lifecycles; use without guilt
The steelman scorecard

VI. Functional core, imperative shell: the consensus nobody announces

In 2012 Gary Bernhardt gave a talk called "Boundaries" that quietly became the closest thing this debate has to a settlement. The pattern it describes goes by "functional core, imperative shell," and once you see it you will notice that every serious system you admire is built this way, whether or not its authors have heard the phrase.

The rule and its economics

The rule: all decisions live in a core of pure functions operating on immutable values. All dependencies, all I/O, all state mutation, all of the world's mess lives in a thin shell around that core. The core decides; the shell executes. The core is where the branching logic and the complexity live, and because it's pure, you can test it with plain assertions, no mocks, no fixtures, no network. The shell has almost no logic of its own; it's a dumb chauffeur that carries data to the core and carries the core's decisions back out to the world. Bugs concentrate where decisions are; decisions are now in the cheap-to-test part. This is not aesthetics. It's an economic argument about where to put your defect surface.

The harness mapping

Map it onto your harness and the fit is almost embarrassing. Functional core: prompt construction, model-output parsing, tool selection, routing decisions, state reducers that fold a new event into the conversation state. All of it is data-in, data-out. Given this context and this model response, what happens next? That's a pure function, and it's also the part of your harness where every bug that matters will live. Imperative shell: the actual API call to the provider, the sandbox process, the filesystem, the vector store, the Postgres write. Note that the shell is exactly where we just agreed objects earn their keep. The sorting rule falls out by itself: FP for decisions, objects for resources. The paradigm war ends not in victory but in jurisdiction.

FP for decisions, objects for resources.

You can see this pattern load-bearing in production agent frameworks right now. LangGraph's whole state model is reducers: nodes return updates, reducer functions fold updates into state, state flows through channels. That is functional programming in the trench coat of a Python framework. Anthropic's published guidance on harnesses for long-running agents has the agent externalize its state into a git repo and a progress file, so a fresh session reconstructs context by replaying durable artifacts. That is event sourcing, the FP answer to persistence, wearing a trench coat made of files. The industry converged on these shapes under pressure from the actual problem. Nobody's ideology survived contact with production; this pattern did.

Actors, Kay's objects, and your agents

Scale the pattern up, Bernhardt says: a big system becomes many small functional cores, each wrapped in its own imperative shell, communicating by messages. Then he points out that he has just described the actor model, the architecture Erlang has run telecom switches on since the eighties. Isolated stateful units, no shared memory, message passing only, supervision trees for failure. Now hold that next to Section II. Independent units, hidden state, communicating exclusively through messages, late-bound. That is Alan Kay's definition of object-oriented programming. The functional-core-imperative-shell endgame and the original meaning of OOP are the same architecture, arrived at from opposite shores. And both of them are, structurally, a description of a multi-agent harness. Your agents are actors. Your actors are Kay's objects. Your objects' internals are pure functions. The paradigms were never enemies; they were partial views of one design that this industry keeps rediscovering whenever the systems get big enough and concurrent enough to punish anything else.

VII. Hexagonal architecture without the class ceremony

You already run hexagonal architecture, so I'll skip the sales pitch and go straight to the assumption that ports require interfaces, and interfaces require classes, and therefore the FP guy has to dress up in OOP costume at every boundary.

Ports as function signatures

Alistair Cockburn's actual formulation of ports-and-adapters never mandated classes. A port is a contract: a statement of what the core needs from the outside world (a driven port: "I need to persist this," "I need to call a model") or what the outside world may ask of the core (a driving port: "handle this request"). In Java, contracts get spelled interface because that's the only word Java knows. In a functional codebase the same contract is spelled as a function signature, and the whole pattern gets lighter. Mark Seemann has a piece bluntly titled "Functional architecture is Ports and Adapters" arguing that disciplined FP doesn't merely permit this architecture, it produces it by default, because purity physically forces I/O to the edges. The hexagon isn't something you impose on FP code. It's what FP code looks like when you draw it.

The hexagon isn't something you impose on FP code. It's what FP code looks like when you draw it.

Concretely, a port becomes a function type, and an adapter becomes any function matching it. Dependency injection, stripped of the framework mysticism that grew around it in the Spring years, is just passing functions as arguments:

# Ports: contracts, written as function shapes (Protocols if you want mypy's blessing)
CallModel = Callable[[ModelRequest], ModelResponse]
RunTool = Callable[[ToolCall], ToolResult]
PersistEvent = Callable[[Event], None]

# Core: pure decisions, blind to infrastructure
def step(state: AgentState, call_model: CallModel, run_tool: RunTool) -> tuple[AgentState, list[Event]]:
    ...

When the core needs several capabilities, bundle them in a frozen dataclass and inject the bundle; a record of functions is a port collection without an inheritance tree in sight. And notice what testing becomes: a fake adapter is a lambda. Not a MockToolRunnerFactoryBean configured across forty lines, a lambda, written inline in the test, readable in one glance.

The composition root, where the worlds shake hands

The composition root, the one place where everything is wired together, is where the two worlds shake hands. Your adapters wrap object-shaped SDKs, because Section V was right and the ecosystem is object-shaped: somewhere a Pydantic client and a connection pool and a sandbox object get constructed, managed, and torn down. But each one gets exposed inward as a plain function satisfying a port. Objects at the rim, functions in the core, and the hexagon's boundary is precisely the line where one becomes the other. That's the entire architecture. It fits on an index card, and it resolves a debate people have been having for forty years.

One vertical line marked composition root. Outside it sit three object-shaped SDK clients: a model client, a connection pool, a sandbox. Inside it sit the same three capabilities written as plain function types: CallModel, RunTool, PersistEvent.
A port is a contract. In a functional codebase that contract is spelled as a function signature, and dependency injection is passing functions as arguments.

VIII. The IR thesis: build the harness like a compiler

Now the LLVM material, because your instinct here is the strongest card in your hand and you should know its formal name.

The M×N problem

The classical problem in compiler engineering: M source languages, N target architectures. Build a dedicated compiler for each pair and you owe the world M times N compilers. Twenty languages, fifteen targets, three hundred compilers, game over. LLVM's answer is the intermediate representation: every frontend (Clang, rustc, Swift) compiles to LLVM IR, every backend compiles from IR to its target, and the bill collapses from M×N to M+N. This is "the M×N problem," and the IR move is one of the highest-leverage structural ideas in all of software, because it shows up anywhere many producers must reach many consumers. Protocols are IRs. SQL is an IR between query writers and execution engines. Arrow is an IR between data tools. Once you have the lens, you see it everywhere, and you should, because it's the same lever every time: stop multiplying, start adding.

Twenty source languages against fifteen target architectures. Built pairwise that is three hundred compilers, struck through. Routed through one intermediate representation it is thirty-five.
Every frontend compiles to the same representation and every backend compiles from it. A new provider costs one adapter instead of fifteen.

Stop multiplying, start adding.

Your harness through the lens

Look at your harness through this lens. On the frontend side, the M: model providers, each with their own API shape, tool-calling format, and streaming quirks, plus the agent frameworks, plus your own prompt formats, and all of it churning quarterly. On the backend side, the N: execution targets. Sandboxes, browsers, MCP servers, databases, sub-agents, human approval gates. If your orchestration logic speaks directly to providers and directly to executors, every piece of intelligence in your system is welded to vendor surfaces on both sides, and you are personally paying the M×N integration tax forever, with interest, in a market where the vendors mutate faster than you can amortize the integration work.

The compiler-shaped harness refuses both weldings. Define your own representation of the things that matter: a conversation, a tool call, a plan step, an observation, an event. Frontend adapters translate each provider's dialect into your representation. Backend adapters translate your representation into each executor's dialect. The core, the part that holds everything you actually know about orchestrating agents, speaks only IR. Now a new model provider costs one frontend adapter. A new tool runtime costs one backend adapter. Your routing logic, your retry policy, your evals, your replay tooling, all of it works unchanged across every combination, because none of it ever learned a vendor's name. In a domain with no settled standards, where every quarter brings a new API to integrate and a new framework to evaluate, the IR is how you convert vendor churn from an existential threat into a line item.

One design, seven names

And here the previous seven sections converge, the same idea at maximum altitude. The IR is immutable data; transformations over it are pure functions: that's your functional core. The adapters at both rims wrap stateful vendor SDKs in objects with lifecycles: that's Section V, objects earning their keep at the boundary. Frontends and backends are ports and adapters: that's the hexagon, with the IR as the contract language every port speaks. Events in IR form, persisted in order, give you event sourcing, replayable sessions, and evals against recorded reality. Entities-as-data processed by systems-as-functions: the ECS sensibility, generalized beyond games. One design, seven names. The compiler people just got there first and wrote the best documentation.

IX. Closing the memo: the scorecard

Howard Marks likes to say that you can't predict, but you can prepare. In architecture the equivalent is: you can't know which framework wins, but you can position so it doesn't matter. So, positions, stated plainly so you can deploy them in the next conversation with a principal who raises an eyebrow.

The four positions

On OOP: the word names two different things. Inheritance-as-modeling is discredited by OOP's own canonical literature; the GoF said "favor composition over inheritance" in 1994 and the industry's most experienced practitioners have agreed ever since, whether or not they've noticed. Objects-as-resource-containers are correct engineering, and you should use them freely at your system's edges without feeling like you've compromised anything. When a senior says "you still need OOP," agree, specifically and precisely: "at the shell, for stateful resources with lifecycles, absolutely; for domain modeling I keep the core pure." You'll have ended the argument by drawing the jurisdictional line they were gesturing at all along.

On FP: it is the core rather than the opposing team. Decisions as pure functions over immutable data, state evolution as reducers, persistence as events. Your data engineering formation is not a limitation you're compensating for. It is, for this particular decade and this particular problem, the correct prior arrived at early.

On ECS: keep it as your mental model for any system with many entities and orthogonal behaviors, cite Cities: Skylines 2 on Unity DOTS as the documented case, describe Factorio and Dyson Sphere Program as data-oriented and composition-heavy rather than confirmed-pure-ECS.

On the harness: build it like LLVM. Own your intermediate representation. Adapt vendors at the rim, keep intelligence in the core, persist events, replay everything. M+N, not M×N.

Four positions written out to be carried into an argument. On OOP: at the shell, for stateful resources. On FP: the core, where the bugs live. On ECS: many entities, orthogonal traits. On the harness: own the IR, pay M plus N.
Agree with the senior engineer precisely. At the shell, for stateful resources with lifecycles, and keep domain modelling in a pure core.

The meta-lesson

The paradigm war was never a war; it was a jurisdictional dispute that got marketed as a holy one, and the people who profit from holy wars are rarely the people fighting them. Kay's objects, Bernhardt's cores and shells, Cockburn's hexagons, Erlang's actors, the game industry's ECS, and LLVM's IR are six communities' independent discoveries of the same shape: pure decisions in the middle, managed state at the edges, messages in between, and a common representation so the pieces can change without the whole thing caring. You found that shape through GPU code and data pipelines instead of through Smalltalk and Erlang. Fine. The mountain has many trails. The view from the top is identical, and the people warning you to be careful on the way up are, it turns out, describing the same summit from a different face.

Be skeptical of anyone selling certainty about paradigms, including me. But the weight of evidence, the convergence of six independent traditions, and the specific demands of agent systems all point the same direction. You're not behind on OOP. You're early on the synthesis.

You're not behind on OOP. You're early on the synthesis.

Sources worth your time: Gamma, Helm, Johnson, Vlissides, "Design Patterns" (1994), p. 20, for composition over inheritance from the source. Alan Kay's 2003 email defining OOP as messaging, widely archived. Gary Bernhardt, "Boundaries" (2012). Mark Seemann, "Functional architecture is Ports and Adapters" (2016). Robert Nystrom, "Game Programming Patterns," the Component chapter, for the inheritance-to-composition migration in game engines. Unity DOTS/ECS documentation for the Cities: Skylines 2 substrate. The LLVM project's own architecture docs for the M×N argument. Anthropic's "Effective harnesses for long-running agents" for state-as-durable-artifacts in production harnesses.

#paradigms#ecs#compiler#harness

The weekly digest.

Sunday delivery. The week's best note, the worst one I almost published, one thing I read that changed my mind.

SUBSCRIBE >