The Best Way to Vibe Code is Literate Programming
Reviving One of the Most Obscure Software Development Paradigms for the Age of AI
Literate Programming (LP) is a decades-old, obscure programming philosophy that emphasizes human understanding over mere machine execution by intertwining code and prose. It is the precursor to Jupyter Notebooks, nbdev, Quarto, and many other documentation-friendly coding paradigms. That is, in the same sense as LISP is the precursor to all functional languages, but none lives up to the expressiveness of LISP.
For a long time, Literate Programming has been mostly a niche paradigm, embraced only by the nerdiest of the nerdiest among us. However, as we incorporate more and more AI into our development, I believe LP is going to make a comeback. I argue that despite its current niche status and challenges, LP, especially when supercharged with specific tooling and advanced AI, is the optimal paradigm for coding with AI.
In this article, we'll first define Literate Programming and explore its core components, distinguishing it from similar approaches like Jupyter notebooks. Next, we'll tackle the historical reasons why LP hasn't become mainstream. Then, the core of the argument: why Literate Programming is uniquely suited for AI-assisted coding. Finally, we'll look to the future, discussing how targeted AI development and advanced tooling can further enhance LP, concluding with a concrete example of my own work in this space.
What is Literate Programming?
Let’s start from scratch. What exactly is Literate Programming? At its heart, LP fundamentally shifts our perspective on what a program is. Instead of viewing code as a sequence of instructions for a machine, LP frames a program as a continuous, flowing narrative designed for human comprehension. Code snippets embed directly within this prose, illustrating logic and design choices as part of a cohesive story. Think of it this way: the program is the documentation, and, crucially, the documentation is the program.
If you want to see it for yourself, here is a massive collection of LP programs in several programming languages.
This isn't just about adding comments; it's a profound reordering of priorities. Clarity and explanation become the primary goals, with executable code as a byproduct of that clear narrative.
To truly understand LP, we need to look at its core components. It all begins with the literate source document, a continuous flow of prose with interspersed code snippets. So far, this isn't any different to a typical blog post with explanations and code. But here comes the key twist.
Within the code snippets in a literate source, you can define macros, named placeholders that reference other blocks of code. These macros enable recursive expansion. They can reference other macros, allowing a deeply hierarchical and top-down explanation of your program. You can start with a high-level concept, then progressively drill down into details, always maintaining a clear narrative flow.
Once you craft this human-centric narrative, two key processes come into play: tangling and weaving. Tangling extracts the executable code from your literate source file, pulling only the parts necessary for the machine to run. Conversely, weaving generates human-readable documentation (like a PDF or HTML file) from the literate source, resulting in a beautifully formatted explanation complete with code examples, all derived directly from the source file.
If you want to understand LP better (or you are just curious about it), there is a community website that
Isn't This Just …?
You might think, "This sounds a bit like Jupyter notebooks, Quarto documents, or even what Pandoc does." And you'd be right to some extent. Tools like Jupyter notebooks, Quarto, and Pandoc certainly share some principles with Literate Programming, primarily intertwining code and explanatory text. They revolutionized data science and technical communication by allowing code, output, and narrative to coexist in one document.
However, here is the critical distinction: none of these have fully featured recursive macro expansion. This prevents them from achieving the deep disentangling power central to classical Literate Programming. While they let you intersperse code and text, they do not provide the same hierarchical abstraction and narrative control that LP's macro system offers. This means they do not quite realize the full vision of the program as documentation in the same profound way.
Crucially, both Jupyter and Quarto (which is ultimately based on Jupyter) require code cells to be executable. This means they need to be complete, correct code. All necessary references must resolve beforehand. Any methods, variables, or symbols used in any cell must exist by the time that cell executes.
This goes head-to-head with LP's core idea, which forgoes any structural requirements for executing the code in favor of using the best structure to understand the code. So, in LP, you can leave half of a method undefined because its details are not important at that moment.
Why Literate Programming Isn't Mainstream
Given its elegant philosophy, it's fair to ask: why isn't Literate Programming the default? Originated by the legendary Donald Knuth, LP largely remained an academic pursuit or a niche interest. Several reasons explain its limited adoption.
Perhaps the most significant hurdle has been tooling. Providing robust linting and type checking on disentangled source code proves incredibly difficult. Imagine trying to analyze code scattered across a narrative, with pieces defined by macros that might expand elsewhere. The current workaround involves linting the tangled files (the extracted, executable code) and then trying to project any errors back to the original literate source. This process is complex and notoriously hard to make reliable across diverse programming languages. There is simply no robust tooling support for this yet.
Another more subjective, but still yet very real limitation concerns collaboration at scale. When multiple developers work on different architectural components of a large project, LP's narrative-first approach can complicate simultaneous development. It requires a different way of thinking about how components interact and are documented, which can hinder its adoption in fast-paced, multi-developer environments based on pull requests and frequent code reviews.
Finally, LP demands a significant mental model shift from developers. We are deeply ingrained in the idea of writing sequential code files. Prioritizing explanation and narrative structure over the immediate gratification of seeing runnable code requires a different mindset—one that many developers, under project pressures, find hard to adopt.
LP is the Best Paradigm for AI-Assisted Coding
Despite these historical challenges, I firmly believe that Literate Programming is uniquely suited for working with AI. In fact, it might just be the best paradigm for AI-assisted coding.
The most compelling argument is that, when applied correctly, a Literate Programming document inherently contains all the necessary explanations and context for an LLM to understand it. By definition, no code exists that isn't fully explained within the literate document itself. This completely eliminates the need for separate, additional, or laboriously crafted prompts for your AI assistant. The AI can simply read your literate program, understanding the why alongside the what.
Furthermore, AI fundamentally differs from traditional development tools. Compilers are notoriously rigid, demanding perfect syntax and strict adherence to language rules. This rigidity historically made working with disentangled LP source code difficult. However, AI is far less hindered by these traditional tooling issues. It remarkably adapts to interpreting "pseudo-code," understanding natural language explanations, and even working with partially formed ideas. This aligns perfectly with LP's nature, where the human-readable explanation often precedes the perfectly formed executable code.
AI's capabilities extend beyond just spitting out code. Yes, AI can effectively generate code snippets that fit seamlessly within your literate explanation. But here’s the crucial part: AI can also help you explain and refine the literate (textual) part of the program itself. It leverages its proficiency in understanding both natural language and code to suggest better explanations, clearer analogies, and more precise prose, elevating the quality of your documentation as much as your code.
Given these advantages, LP, especially reinforced by AI, is perfect for specific scenarios. Consider short, learning-based projects like tutorials, coding books, or personal learning journeys. For single authors, AI can dramatically amplify both the learning process and the creation of highly understandable educational content.
It's also excellent for exploratory coding. When prototyping or learning in a new problem space, LP combined with AI can prove invaluable. AI assists in articulating your hypotheses, assumptions, and findings, promoting clearer thinking and ensuring that your exploration process itself is well-documented and understandable.
How to Make it Even Better
First, you don't need any new AI-powered editor to leverage literate programming today. Even the simplest AI code assistants are incredibly effective with literate programming today. Why? Because the necessary prompt context for these tools already exists within the explicitly explained literate source. The AI isn't guessing; it reads a meticulously documented narrative that includes the very code it needs to assist with.
However, to really push Literate Programming from niche to mainstream, we will need some targeted AI development and advanced tooling.
The most critical next step involves training and fine-tuning coding agents specifically to produce literate source. This isn't just about generating code that happens to have comments; it means AI mastering the leveraging and generation of macros. Macros, with their recursive expansion capabilities, are by far the most powerful feature of LP. If AI can master their use, it will unlock LP's full potential. Imagine an AI not just writing functions, but designing and documenting the conceptual hierarchy of your program.
Secondly, we also need proper tooling that goes beyond simply feeding the AI the current document. A proper LP workflow would perform partial or recursive tangling of the literate source during AI inference. Why is this crucial? It allows the AI to simultaneously see the exact current literate programming source (providing the rich human context) and have a peek at the partially generated tangled source (providing the immediate machine context). This dual perspective will lead to far more accurate, context-aware, and intelligent suggestions from the AI.
Ultimately, I envision a shift from a directed, task-based interaction with coding agents to a truly real-time, continuous collaboration. As the developer writes in the literate programming source, the AI agent wouldn't just wait for a prompt; it would constantly suggest improvements, offer completions, and refine both the code and the accompanying explanation. The AI acts as a buddy looking over your shoulder, providing immediate insights and pointing out areas for betterment in both the code logic and its narrative explanation.
Conclusion
I believe Literate Programming is the ideal paradigm for coding with AI, especially for solo projects. It’s a natural fit, allowing AI to contribute to the explanation of the program as much as its execution.
Of course, this paradigm won't fit all coding methodologies or project types. I remain uncertain about its scalability for very large projects with multiple developers handling distinct architectural components. However, for smaller projects and those that lend themselves to vibe coding—where exploration, learning, and clear articulation are paramount—its fit is incredibly strong.
To put my money where my mouth is, I recently built a Literate Programming tool in Rust. It's aptly called illiterate. This tiny CLI tool takes literate source files and performs all macro expansion and tangling across any programming language you throw at it.
To be honest, I kind of hacked it over a weekend as a personal quest to learn Rust, but it was also an exercise in LP, because its own source code is written entirely in the literate programming paradigm. It serves as a living, readable demonstration and, in essence, a blog article of the very concepts we've discussed today. You can read the annotated source here.
I encourage you to explore literate programming, especially in this exciting new context of AI. Perhaps take a look at illiterate as a starting point. And, as usual, leave me any comments if you have follow up questions or if you want to discuss any part of this article in greater depth.
I always thought literate programming was a beautiful way to develop. In practice, it always ended up being more trouble than it was worth, even with things like Emacs and Org-Mode making it fairly streamlined.
Practically speaking, it always made more sense just to do... normal coding... and get things done.
It's definitely an interesting idea that LLMs may bring it back and make it actually practical.