#LLMs

Why LLMs Aren't the Next Generation of Compilers

Tech Essays Reporter
4 min read

While LLMs can translate natural language into code like compilers translate high-level languages into machine code, they fundamentally differ in their lack of precise semantics and the dangerous level of control they relinquish to users.

The question of whether large language models represent the next evolution in programming abstraction has been circulating since Andrej Karpathy's provocative claim that "English is the hottest new programming language." At first glance, the analogy seems compelling: just as compilers translate high-level languages into machine code, LLMs translate natural language into executable programs. But this comparison, while superficially attractive, masks a fundamental difference that could reshape how we think about software development.

The core of the compiler analogy rests on a simple premise: programming is about making computers do things, and computers are fundamentally dumb machines that require exact instructions. Early programmers worked directly with bits and arithmetic logic units, manually mapping their domain concepts onto low-level operations. The genius of higher-level languages was that they introduced abstractions—variables, loops, data structures, functions—that didn't exist in the underlying hardware but dramatically reduced the mental burden on programmers.

These abstractions work because they come with precise semantics. When you call malloc in C, you know exactly what guarantees you're getting: a pointer to at least the requested memory size, suitably aligned, or NULL. These guarantees aren't just documentation; they're testable, verifiable contracts that allow programmers to reason about their code without understanding every implementation detail. The abstraction layer is bounded by formal language definitions that specify exactly what behaviors are allowed, forbidden, and guaranteed.

LLMs fundamentally break this pattern. Natural language, by its very nature, is underspecified. When you say "give me a note-taking app," you're not describing a single program—you're describing an enormous space of possible implementations. The LLM must fill in countless details: what data model to use, how to handle edge cases, what security posture to adopt, what performance tradeoffs to make. These aren't just implementation details; they're functional choices that determine what your program actually does.

This creates what I call the "specification gap." Traditional programming languages force you to confront design decisions because you have to write the code yourself. With LLMs, those decisions get made for you, often invisibly. You might end up with something Notion-like, Evernote-like, or entirely novel, and you won't know which commitments were made until you start using the software. The danger isn't just that these choices might be wrong—it's that you might not even realize which choices were made at all.

The iterative refinement loop that emerges from this gap is seductive but dangerous. You write an imprecise spec, get an implementation, inspect it, refine the spec, repeat. This feels productive, but it transforms you from a producer deliberately constructing software into a consumer selecting from generated artifacts. You're no longer exploring the space of possibilities through conscious design decisions; you're reacting to whatever the model happened to generate.

This touches on something deeper than just technical capability. It plays into a dangerously lazy part of the human mind. We already struggle with writing precise requirements; that's not new. What's new is how directly an LLM can turn vagueness into running code, inviting us to outsource functional precision itself. The semi-conscious slips are telling: accept-all-edits, "one more prompt and it'll be fine," the slow drift into software you don't really understand.

But here's the paradox: LLMs actually excel when given concrete constraints. They're brilliant at optimization, refactoring, translation, and migration tasks—exactly the kind of work that used to be so labor-intensive we'd laugh at the timeline. The key is that these tasks have well-specified targets backed by robust test suites. When the behavior is clearly defined, the LLM becomes an incredibly powerful tool for implementation.

This suggests a fundamental shift in what programming skills will matter most. For decades, the bottleneck has been building software—translating ideas into working code. But we may be entering a world where: if you can specify, you can build. If that's right, then specification and verification become the bottleneck, and therefore the core skill.

The implications are profound. Traditional compilers reduced the need to stare at lower layers by replacing low-level control with defined semantics and testable guarantees. LLMs also reduce the need to read source code in many contexts, but the control you lose isn't naturally bounded by a formal language definition. You can lose control all the way into becoming a consumer of software you meant to produce, and it's frighteningly easy to accept that drift without noticing.

This isn't to say LLMs have no place in programming. They're already transforming how we work, making certain classes of tasks trivial that used to be painful. But we need to be clear-eyed about what we're gaining and what we're giving up. The compiler analogy, while useful as a starting point, obscures the fundamental difference: traditional abstractions reduce complexity by giving us precise, testable guarantees about what our code will do. LLM-based programming reduces complexity by letting us avoid making precise specifications at all.

The future of programming may not be about writing less code, but about writing better specifications. As LLMs become central toolchain components, we'll need ways to strengthen the will to specify, and to make specification and verification feel as "normal" as writing code used to. The question isn't whether LLMs can generate code—they clearly can. The question is whether we're willing to accept the trade-offs that come with treating them as the next generation of compilers.

Comments

Loading comments...