Hacker News new | past | comments | ask | show | jobs | submit login
Literate programming: Knuth is doing it wrong (2014) (akkartik.name)
144 points by signa11 on Jan 9, 2022 | hide | past | favorite | 168 comments



I think it's clear to everyone that Knuth's style of literate programming didn't achieve any sort of mainstream adoption.

However, this article doesn't mention a newer form of literate programming that has gone mainstream. Notebooks! Whether it's jupyter, colab, whatever the Julia one is called, etc. People use literate programming all the time for data science and machine learning.


To me as an org mode and org babel user, notebooks seem to be a very half-assed form or literate programming for the following reasons:

(1) No linking from on cell to another. A sequential execution is assumed or otherwise you need to manually run cells in different order. There might be extensions to help with that. It is not out of the box though.

(2) The documentation language is markdown This does not allow for great technical documents with arbitrary links to arbitrary other parts in the document or inside another notebook. ReStructuredText would have that using link sources and link sinks. org mode can also link to parts in other documents.

(3) I cannot include other notebooks to have it all exported as one whole document or notebook as I can in org mode. In org mode I can even specify at what level some heading should be imported.

(4) The editor in notebooks is not that great compared to any IDE or normal code editor or Emacs.

(5) In a typical JupyterLab environment there is only one programming language per notebook. I know there are other kinds JupyterLab environments, which allow for multiple langs inside the same notebook.

(6) The base format of notebooms is JSON instead of plain text like in org mode. This means diffs are harder to read and version control. Again you need extra tooling for dealing with it well (which does exist).

The literate programming stuff of notebooks has all long been there before notebooks came into existence. It is not like that was the advent of a new form of literate programming. In JupyterLab environments it would take lots of fumbling and tweaking to get something comparable to org + org babel.


Pluto solves¹ many of these problems, but it’s Julia only. Most importantly:

- cells are linked through a dependency graph, so the output you see is independent of the order in which the cells appear;

- each notebook is backed by a plain Julia module, so you can use version control, edit in a real editor instead of only in the browser, and import other notebooks;

- You can create interaction² using HTML inputs, Julia wrappers over those, or go crazy with your own custom JavaScript controls;

- It’s easy to embed LaTeX into the MarkDown, and you can use HTML, so any hyperlinking works.

    1 ‘An introduction to pluto’. LWN. Available from: https://lwn.net/Articles/835930/

    2 ‘PlutoUI demonstration’. Available from: https://pluton.lee-phillips.org/sliders/uiDemo.html


But unfortunately it doesn't have option to turn off reactivity which makes it not appropriate for intensive computation.

I wish it had a sequential mode.


Org mode and everything Emacs has a crazy steep learning curve. I tried using Org for note-taking with formulas, and I remember having to learn a ton of arcane things like Doom Emacs and endless keyboard bindings (Org + Emacs + Vi). I then just switched back to something easy, where I could fluently get my thoughts down without wasting endless time.

Maybe I should give it another go with Spacemacs because I like the idea of Org mode. That might make the learning curve gentler. I hadn't considered that possibility at the time.

I don't understand why Emacs's features aren't exposed via context menus. You can always switch to the keyboard later as you get good.


The learning curve is steep, that is true. I guess one has to invest some learning to get the good stuff. I myself needed 2 attempts at getting into Emacs. One very short one, where I found myself struggling with even saving a buffer, that ended quickly. The other one more serious, investing my free time out of curiosity, what all the Emacs fuss is about. At some later point I discovered org mode and from then on, there was simply no way back for me to something less. Similar for Magit. Or running shells inside Emacs and being able to simply copy anything from a shell buffer, because it is a buffer and not some readonly thingy in a separate window. Very useful.

Now I use org mode almost every day and definitely for all kinds of documentation and notes. It might not be perfect out of the box for formula rendering, but even that can be arranged/configured, so that you can switch to rendered formulas inside your buffer and I have done so in the past.

I have not tried to use Doom Emacs or Spacemacs or whatever flavour. I started with standard Emacs and went with that and still use that. If Doom Emacs or Spacemacs introduce too many new concepts or make the learning curve too steep, perhaps trying standard Emacs will be a better experience.

Not sure what use context menus would have in my personal workflow. Perhaps if they could be opened via keyboard shortcut and then I could select menu items quickly, it would be useful.


There’s some work in this space, such as Nicolas Rougier’s promising notebook-mode[1]. I’m convinced there would be an audience for an OrgBook app that philosophically treated Emacs as an implementation detail. Give it more familiar keybindings, some out of the box nice looking themes, and configure the new context menu functionality as you suggest. Then package it up as something that can be run and installed with or without an existing Emacs.

It’s hard to imagine experienced Emacsers wanting to lead a project that solves a problem they don’t have, but the community is very friendly so whoever took it on would get plenty of help.

[1] https://github.com/rougier/notebook-mode


Emacs has menu bars, tool bars and context menus ootb. Even scrollbars. Most Emacs users disable it.

Simply run M-x menu-bar-mode, or add (menu-bar-mode 1) into your init.el or config.el

Same syntax for tool-bar-mode. Context menu is ootb for me with doom emacs, so that should just work.


I don't understand why Emacs's features aren't exposed via context menus

Mainly I think it is a matter of efficiency...on the implementation side. I mean the hard part about learning something like org-mode (or emacs itself) is conceptual not key bindings.

It's this way because org-mode (or emacs itself) is a powerful tool that users use for decades. The tooling is intended to reward expertise and experience. No amount of context menus will change the fact that the tools are deep and conceptual mastery will take more than a few hours (never mind the thirty seconds the typical app has to engage the user).

Org-mode was written for it's author's use. It was written to get things done. Easy onboarding and massive investment in graphical UI for non-experts wouldn't do that. It requires work to learn. That's true of anything hard.

It's not that I don't sympathize...though it probably can be interpreted that way. It's that using emacs (and org-mode) is analogous to playing the piano, not playing the stereo.

Finally, I while I get the appeal of things like spacemacs, I think it's mostly a distraction. The documentation and tooling is by it's nature derivative...it is by it's nature extra work. Even worse it's extra work that's driven by ideology. Worser still, it's extra work driven by the ideology of Vim versus Emacs. And worst of all, it's rational is emacs-is-too-hard-to-learn.

Emacs is hard to learn, but not too hard. It's harder to learn than a sixteen week college course. But that's most things in the working world.


You can activate a menu, but it only covers very basic things. If you are confined to using only the menu, you might equally well use notepad.

For base emacs, the learning curve is not as steep as vim. I wonder if your problem was trying to do everything at once?

Regardless, emacs really is more of a long-term investment. It will take you years to be fully proficient. Personally, I have gone away from using emacs (including org-mode and vim emulation), because it took too much time to keep the configuration shiny.

My main issue with org-mode is that it's only really a first class citizen within emacs. I've not found any vim/vscode/etc. plugins that emulates the experience to satisfactory degree, and that lock-in means I've given up on org-mode.


Context-menus will land in the next Emacs release and are already available on master. Expect more and more functionality to be exposed via them.

Being emacs, context-menus will also be configurable.

https://github.com/emacs-mirror/emacs/blob/emacs-28/etc/NEWS... https://ruzkuku.com/texts/emacs-mouse.html


As others have said, a big draw for notebooks is keeping the bar low. Sequential ordering is intuitive and doesn't require a complicated additional ui for managing the execution graph.

In fact, the execution graph logic is handled by the user. When it gets too complex, it's a sign that the notebook itself is too complex, and it's time to move a lot of code into libraries. Notebooks /should/ be conceptually simple.

Then again, I bounced off emacs after deciding the complexity didn't actually justify the endpoint: I was already able to write and maintain code with much simpler tools, so why bother? I can use the party of my brain that would otherwise be used for key bindings for other things. So, mileage varies.


To each their own. I want to question though, whether those simpler tools are truly simpler. Lets say you want to use notebooks instead of org mode. You will need to have the following set up, before you can start:

- python3 (OK, mostly a given, though not on Windows)

- pip and depending on your requirements you might need Poetry or Pipenv or whatever else

- Jupyter ecosystem (JupyterLab, Jupyter/notebook server and all their dependencies)

- some extensions for JupyterLab. Those could be frontend and backend. If frontend, then you will also need to have NodeJS on your machine, to have NPM, to have tsc to be able to build JupyterLab with the extension installed.

Quite a few parts there as well.


Especially for analysis tools (as I tend to use colab/Jupiter), the important part of complexity is cognitive load. Installation is a constant, upfront cost in that regard: I have literally never worried about the NPM or nodejs, as they're handled automatically by standard installers... Or already running in shared environments. (And if I'm deeply worried about binary size and dependency depth, I probably shouldn't be working python in the first place, and have other tools I can go to.)

Cognitive load when using the notebook is very low compared to emacs. As has been remarked on often, emacs is probably fine if you've been using it for thirty years, but having to keep track of different key strokes for copy paste depending on what window you're in (emacs vs the rest of the world) is pointless cognitive load. The idioms are so sprawling and out of sync with everything else that the editor is an obstacle in the way to doing what I want.

And while (historically) that perspective may just paint me as a noob, I'll just say that no amount of memorizing editor functionality or micro optimizing configurations will ever help me prove a theorem. If there's another tool that gets the job done and doesn't want to fight me before/while I use it, then it's the better tool.


R notebooks (which are not jupyter-based) addresses (to varying degrees) issues 3-6. But then you are of course stuck inside the R ecosystem, where only Rstudio and emacs are what I would consider fully mature editors.

As a casual rather than power user, I especially dislike the JSON format of jupyter notebooks.


For all the popularity of Jupyter notebooks, I never did understand the appeal. I agree that R notebooks and Emac's org-mode are far more sensible ways of doing things. Not perfect, by any means, but sensible.


I cannot speak to R, but the Emacs learning curve is the primary reason org-mode is not more popular. There may be a break-even point past which learning Emacs is a net benefit, but few people have the time, skill, and inclination to reach that point.


It is a good thing, that slowly other editors gain support for org files. I've seen them at least highlighted in VS code recently, when someone else shared their screen, even if that is but a tiny fraction of what support for org files can mean.


You might like Starboard Notebook (https://starboard.gg), it's in-browser and mixed multi-language, as well as diffable/version control friendly. (I'm building it).

https://starboard.gg


These are interesting to me as a notebook user, because a lot of these I see as advantages of the notebook design.

(1) This is better to me, because it makes notebooks more consistent and easier to jump into. I read english prose sequentially, why not code?

(2) You can do this in all environments I've used with <a> tags.

(3) this would be nice, but I'm not sure it's worth the tradeoff because the editing environment wouldn't correspond as well to the exported document.

(4) true, but I have a vim plugin that make it better

(5) also true, but this is a good thing for simplicity. I don't want to have to keep track of the lexical scope of multiple language environments

(6) There are tools for this, but yeah it's not great. The main issue is saving outputs

It sounds like the goals of notebooks vs org mode are slightly different, at least the way I use notebooks.

For me, notebooks are a tool for sacrificing control and programming flexibility in exchange for easier collaboration and visualization. It's harder to write structured code, but it's much easier to share with a large number of semi-technical people.


When you can define a cell depending on another cell and can expect that other cell to be run after its dependencies have run, then you can make sure, that a cell will always give the correct result, not a different one, because other cells have already run, which mess up the state. It would allow you to scroll to the end and see the last layer of abstraction, just run that, and implicitly also run all its dependencies (other cells).

In a top to bottom typical notebook, you will have to run the notebook from the top. There is a menu item for that, of course. However, what if you also have some cells in there, which do not have to be run? They will waste time unnecessarily or even worse, will change the result. So you will need to take a look at each cell to make sure you really can run everything sequentially. Either the notebook has to be created to make sure, that running top to bottom always works and gives the same result, or you will have to look at everything. When code segments can define their dependence on other code segments, they can be designed to that it does not matter which one you run, it will always give the intended result.


> (1) This is better to me, because it makes notebooks more consistent and easier to jump into. I read english prose sequentially, why not code?

As a counterexample, some things are not important but have to be mentioned. In English, these things end up in the appendix. In Pluto notebooks, these things can be moved to the bottom.

Another benefit of less strict ordering is when debugging. Sometimes cell A fails because of cell B. It can be useful for debugging to temporarily put these cells next to each other


1) Yeah, the execution count is there, but there isn't a built in way to visualize it other than the default sequential cells. I really wonder if there's a useful way to show a notebook as a nodegraph.

2) I can't understand the desire for RST over Markdown. Between simplicity and adoption, Markdown beats almost all mark-up languages -- maybe even html.

3) You can use the `run` command to link notebook executions and use Markdown linking to link the documentation.

4) This isn't true. I use vim in Jupyter. There's plugins for emacs too. Also, many editors like VSCode can edit notebooks directly.

5) This is a weird complaint. I can imagine a few corner cases where mixing languages in a notebook would be nice...but the concept of a multi-language Jupyter kernel doesn't make much sense from a practical standpoint.

6) There are diff tools such as `nbdime`. Between those and a few git commit hooks to leave your notebooks in a consistent execution state, merging versions notebooks isn't any worse than other files.


I lot of these rebuttals are "I can't imagine your use case so it's probably bad/weird/not worth it."


People dismiss modern notebooks as Knuth's style of literate programming, where they are more for exploring and documenting an existing codebase or just experimenting. So people are often arguing over two completely different things.

I love notebooks, made VS Code extensions for Go (gobook) and for Rust (rustnote). Give them a try if anyone is interested, still lots of features to add, but the source code is standard markdown, so the code results can be saved straight to Github and they render there.


That is amazing! Have you considered creating a notebook for TypeScript? I saw Microsoft created an extension but never cared to publish it: https://github.com/microsoft/vscode-nodebook


Yeah I have, I'm just focusing on getting the Rust language server working first which is quite tricky, then I'll be adding other languages.


how about https://github.com/winnekes/itypescript

(also obserablejs) for a different take on it.

I have a feeling notebooks are not as popular for javascript since it's quite limiting relative to what a code sandbox can do.


ObservableHQ changed my life. Coding is fun again. I use it to quickly experiment with new libraries, fast prototypes, as proof of concept building, NLP stuff, etc. And I can do it all on my phone!


Observable's evaluation model confers a big advantage too - you're free to write the prose and define constants, variables and imports in any order you like. Multiple times have I been able to write some prose that reads well, but forces things to be defined in an order that is not the same as its evaluation order. And if I don't want anyone to see some code, I stuff it at the end of the notebook (often imports).

That aside, I completely agree - Observable has brought back a lot of the joy of programming I had when I first started. Congratulations to Bostock and the team.


Org Babel can be useful for explorative programming (emacs-jupyter), devops-y stuff: more permanent than REPL but less structured, more fluid compared to the usual code practices.


The mistake of Knuth is instead of "Literate Programming", he made it, or at least let most people take it, into "Literate + Programming". Depend on which part you focus on, it is either "code" with copious amount of "document", or "document" with verifiable "code". Many "literate" programs are in fact the former. The modern "Notebooks" are the latter*. Neither is "Literate Programming".

With "literate Programming", it is one product, the program! Ideally, it is just readable code, code that human can read literally. Since human mind works differently from computer, so the literate programming environment need transform the literate code into a form that compiler can optimize. I believe this idea is inside Knuth's LP. But early attempt is always limited by technology. For example, Knuth's system is based on token parser. Token is not the unit of comprehension for human. Thus he can't do much with the Pascal part other than "tangling" the code as is. Then he had to supplement his goal with English or "document". But if we start to develop the framework beyond "tangle", we'll see a new page in literate programming.

* The "Notebooks" (the noun) implies we are emphasizing the product as documents. However, nothing prevent people to use "Notebooks" to program. Then immediately one will see how limited it is. "Notebooks" does not "tangle" code. Really, there is not much you can do with the code part. For example, try use the "Notebook" form on a C program.


> Whether it's jupyter, colab, whatever the Julia one is called, etc

The Julia one is called jupyter.

Jupyter stands for Julia, Python, TeX and R.


In fact Jupyter is just a mashup of Julia, Python, and R (no TeX). The commenter may have been thinking of Pluto¹, which is Julia-only but a big improvement over Jupyter.

    1 ‘An introduction to pluto’. LWN. Available from: https://lwn.net/Articles/835930/


yes, jupyter is more or less a generalization of ipython, which has been around since 2001. the notebook aspects and language agnostic parts were split off to target python-like languages (julia and R), while ipython became a python kernel for jupyter (like irkernel and ijulia).


You are of course correct, but 'the Julia one' is probably actually Pluto, since it's Julia only.


In my view, notebooks are the exact opposite of literate programming, and it's a shame some people seem to think it is its pinnacle instead.

The focus on a notebook is the report, not the code. In practice this means that the report structure dictates code structure, not the other way round.

By contrast, the focus of literate programming is the code. Any "reporting" done is strictly to enhance the code, and not the other way round.

I've seen far too many codebases that should have been "code generating a report" reverse that logic and implemented as notebooks instead, in the expectation that the underlying code can simply be exported from the report. Good luck if you need to work on someone's code of that kind. It's its own special kind of hell.


On the other hand, I think the main point of literate programming was to make programs easier to maintain, and I constantly hear about the big gap between developping a notebook and pushing the code into production. Notebooks feels more like an answer to how to quickly experiment rather than how to make knowledge last.


These are entirely different things. Literate programming tells you what the code does in large code bases and why it is correct.

Notebooks are used for small snippets, many of them trivial. The comments usually tell you what the buggy snippet is supposed to to. They are more like Powerpoint.


Aren't modern notebooks a form of Knuth's style literate programming, are there any differences? True, they are more mainstream in analytics where there's a need to explain concepts/results more than the logic of the code itself.


Modern notebooks are closer to Stephen Wolfram's Mathematica notebooks.


Implied by arbitrary chunk order, chunks don't need to be stand-alone (or even syntactically valid, but that's probably asking for trouble):

    int main(int argc, char **argv) {
        <<main-parse-argv>>
        // ...
    }


Knuth's style allows code to be written in any order using named chunks.

AFAIK, there are some types of notebooks that allow the use of chunks, but I've never seen people writing in those.


Org babel is the only one that works well, tbh.


> whatever the Julia one is called

https://github.com/fonsp/Pluto.jl


Yes, my productive editor becomes useless with that kind of file content.


I just want to point out that Jupyter and ipynb are separate things.

ipynb files are shit, but Jupyter doesn't have to be. For example, VS Code supports Jupyter without ipynb. This way you can end up with files which, unlike ipynb, are valid in the target language (e.g. python, julia) AND play nice with git, but are still interactive thanks to Jupyter.

In fact, the above realization - that there are IDEs that support Jupyter without ipynb - has been the biggest boost to my productivity in 2021. This came in the form of being able to version my interactive jupyter files.


I did not know that. I may have to revise my opinion.



You wrote "There's a fundamental problem with generating a beautifully typeset document for a codebase: it's dead. It can't render inside just about any actual programming environment (editor or IDE) on this planet, and so we can't make changes to it while we work on the codebase."

Sorry, that's not correct. When writing a literate program you should also include a chunk containing a Makefile. Extract the Makefile and let it construct the program.

I did this with the whole Clojure programming language. The whole source code for all of Clojure, as well as the test cases, are in the PDF. The sequence (from scratch is):

Extract the Makefile from the Latex document. Run the Makefile. This:

  1) extracts the code and tests

  2) compiles the code

  3) runs the tests

  4) recreates the PDF from the Latex
So you edit the Latex text and/or code with any editor and then re-run the Makefile. This will rebuild your program, runs the tests, and re-makes the PDF.

If you have the PDF open next to your editor it is likely that the PDF viewer will reload the changes immediately.

This ensures that (a) your program compiles, (b) your program passes tests, and (c) the PDF builds correctly.

I've been working on literate programs for years. See https://en.wikipedia.org/wiki/Axiom_(computer_algebra_system.... I've even given a talk on the subject See https://www.youtube.com/watch?v=Av0PQDVTP4A).

I find literate programming to be the MOST important change in programming since I started in 1970.


+1 for the talk on "Literature Programming in the Large" (linked in the post above.)

Mr. Daly, your chat was formative for me as I made the switch to literate programming (primarily with org/org-babel) in most of my work and personal projects. While I'd love to see what was in your slides, I'm glad the talk turned out "off the cuff." Thank you!


The slides basically show examples from other literate programs.

I also intended to give a live demonstration of literate code development, as outlined above.

It only takes a minute or so to start from the PDF to create a running, tested version of Clojure running in Java, along with a new PDF. I intended to demonstrate changing both code and data in real time.


While you're editing code (which is most of the time), aren't you still reading and working with the non-typeset version?


Well, you're editing the Latex version of the literate program. But since my development "style" is to make small changes, rebuild, test, and re-create the code and PDF, my "thinking" isn't focused on the "non-typeset" version.

Pick up a physics textbook. Copy down all of the equations. Throw away the textbook and learn physics only using the equations. That's what programmers do. They write down the "equations" but fail to explain the ideas.

Equations, like programs, are really just an "iconic form" of an idea. The idea, however, lives in the surrounding text.


My point is: what I believe the author meant when he wrote that a typeset document 'is dead', is that you're not actually editing the typeset document. You're editing the code, which is then rendered - maybe instantly and continuously, but still.

You're not __directly__ editing the "nicer" representation.


You also aren't directly editing the observable runtime behavior of the system, yet programmers seem to be able to handle that jump regularly.


Isn’t the tangle-untangle process fundamental in literate programming?


There is a trivial C program that is also in the document. It does the actual "chunk" extraction from the Latex. The Makefile uses it to extract named chunks.

The program scans the Latex looking for named "chunk" blocks. Each named block is put in a hash table under the name. Once the scan is complete you use a provided command line argument to extract the named chunk to a file. Of course, the chunk can include other chunks and these get extracted and expanded inline. So

tanglec myliteratefile.tex achunkname > myfile.lang

This creates a hash table from "myliteratefile.tex" containing named chunks, then prints the chunk "achunkname" to stdout (recursively expanding chunks embedded in other chunks). The Makefile can extract Java files and put them in the proper directories.

It really is a trivial C program. Make a hash, print a hash key. This would be a simple interview test question.

(Actually I use Lisp, mostly because I always code in Lisp these days, but lets not start that war.)


I wouldn't say it's absolutely needed in a language like Haskell, where functions and types can be defined in any order. So if you want to defer implementing something until later in the document you can just make it a function and get back to it later.


Lisp has the same ability to define things in any order.

Java wants specially named paths to files. The Makefile can create the directory structure and use the "tangle" program to extract the files to the correct place. That's what I did in the Clojure example.


Newsflash: a PDF viewer is not a programming IDE.


I do literate programming in emacs via org-mode. The output pdf is viewable in emacs, and it auto-reloads in change. In fact I can have it switch to the pdf buffer on weaving the pdf.


I see that as a separate text editor and pdf viewer but I guess it blurs the lines a bit.


Once a program has been developed and the developers have moved on to other tasks it needs to be maintained. The fundamental problem is that if you modify code you didn't write, you don't see the big picture and you don't understand the reasons why the code is written the way it is. Thus changing code without a larger context is almost always going to introduce new bugs.

The only way to correctly change code is to deeply understand the implications of the change. This requires a deep understanding of the code and an awareness of the big picture. Yet the "why" of code is rarely ever written down in standard programming practice. The goal is only to elaborate the "how" so the machine can perform the task. The programmer communicates with the machine.

Literate programming, as used in Axiom, is an attempt to communicate with other users, developers, and researchers in addition to the machine. The goal is to have the program read like a story so that others can understand the rational, the theory, the choices, the implications, and the implementation context as well as the "how".

This code is intended to live forever but it is highly probable that you will not. Write to communicate with the next person to pick up the torch. When you explore code, write down what you learn. When you change code, explain why you made your choices. When you write new code explain what others need to know to maintain it.


My very strong suspicion is that a program written in the “literate” style and then maintained a long time is very likely to end up with its comments and implementations disagreeing, making it worse, in this respect, than one without comments. I say this because this has been my observation even with much more modest amounts of comments. Keeping them in sync with the code is always a struggle.


That is, for me, the genius of LP. It's not just a "style" you garnish on top of your code. When you do LP you become more likely to reread the documentation as you maintain the program. Which makes it easier to keep it up to date.

On the other hand, all the LP programs I've seen were built by people who care about documentation. Perhaps we would have managed to keep things up to date even without LP. There may be a selection bias here.

Either way, I'd say you don't know what you're talking about when it comes to daly's Axiom (http://axiom-developer.org). It doesn't just end up with outdated comments, that's a serious charge to level without concrete evidence.

Similarly, I'd be extremely interested in outdated comments in https://github.com/akkartik/mu/tree/main/linux/bootstrap which is written in the LP style I described in OP. I consider outdated comments to be bugs.


The main problem with maintenance of programs is that, when changing an assumption ‘deep down’ in the code, about nobody is going to check the chain of logic from there throughout the code.

IMO, LP-style programming doesn’t change that. If the problem happens less with programs written in LP-style, I agree with you chances are it’s because people who write in that style tend to be more careful.

What would change it is to require programmers to supply proofs about their code. Unit tests don’t guarantee that, but have the benefit of being easier to write.


But you're expanding the scope now. Yes, LP isn't going to guarantee you always preserve semantics in any change. But hopefully we can agree that it's not going to make it more difficult.

Unit tests or proofs are extremely useful. You should still have them in Literate programs! They're complementary tools.

My Literate system does explicitly assume tests unlike earlier systems I was dissatisfied with. Check out all the tests in https://github.com/akkartik/mu/tree/main/linux/bootstrap. For example, https://github.com/akkartik/mu/blob/main/linux/bootstrap/013...


I have to object here and say that out-of-date comments are better than no comments. I've worked in organizations with "no comments" policies and having no clue what people ever planned to achieve with a piece of code is worse than some old misguided explanation for sure.


I wouldn’t support a no-comment policy, but I’ve wasted a lot of time thanks to misleading comments.


Notices a kindred spirit

You get it. I get it. Unfortunately every intro class inculcates generous commenting, a "best practice" further promoted by corporate proceduralists and mediocre programmers who inexorably become managers.

A comment longer than 160 characters is always weak-sauce justification for logic written awkwardly.


For the most part comments I appreciate are something like "the obvious thing here is xyz, but we can't do that because of this goofy limitation of software we're integrating with."


You have to assume that comments are out of date, then it's easier to make use of them.


But out of date in which way? It could be anything from some extra cases to literally the opposite of what the comment says to the code has changed so much it’s not even clear what the comment refers to anymore.


There are a few of these longer, explanatory comments in the code-base I currently work on, and while we do generally try to keep them up-to-date, personally I find they've become difficult to read. As the code evolves you have people trying to splice sentences into the middle of a paragraph, different people have different writing styles, some aren't native English speakers. Unless you're willing to rewrite the whole thing every time you make a non-trivial change it seems to end up a bit of a mess. As one co-worker put it "you need to read the code in order to understand the comments".


This is mainly a problem for “how” comments and less so for “why” ones. “How” comments are almost always worthless at best, but “why” comments are vital for large code bases. Literate programming should consist of “why” comments and if the why changes and thus the implementation does, the commentary ought to as well.


You're right if juniors take over, but with experience I've learned to understand that things are done a certain way for a reason. Sometimes they didnt see a bigger pictures but many time they did and something blocked them from solving the problem Im trying to solve in maintenance mode.

Commenting too much in the code simply makes my work impossibly hard because I need to juggle with contradictory statements. I ve found that always committing with a ticket reference and keeping tickets forever allows me to go back sometimes decades ago to understand the problem they were solving at the time, for each layer of the code im looking at (each line having an history).

I read comments now with suspicion as sometimes the comment is a commit 20 years ago and the associated line is 5 years old. Should it have always been kept in sync ? Yes. Could it have been ? I've never seen it done properly.


Having no comment at all is strictly worse than having one you're suspicious of and are free to ignore. Leaving good, durable comments is a skill you learn over time, but the solution is not to tell juniors to leave none.


    Yet to me, literate programming is certainly the most important thing that came out of the TeX project. Not only has it enabled me to write and maintain programs faster and more reliably than ever before, and been one of my greatest sources of joy since the 1980s -- it has actually been indispensable at times. Some of my major programs, such as the MMIX meta-simulator, could not have been written with any other methodology that I've ever heard of. The complexity was simply too daunting for my limited brain to handle; without literate programming, the whole enterprise would have flopped miserably.

    If people discover nice ways to use the newfangled multithreaded machines, I would expect the discovery to come from people who routinely use literate programming. Literate programming is what you need to rise above the ordinary level of achievement. But I don't believe in forcing ideas on anybody.
-- Knuth, Donald


Thanks for all the quotes. This one's my favorite of them, I think. I've chatted with you in the past, and I think we largely see eye to eye. I love LP! I still use it. Here's an x86 VM I maintained for several years: https://github.com/akkartik/mu/tree/main/linux/bootstrap. This (old) essay is merely thoughts on _how_ to do LP. We have some disagreement there, but I hope you'll agree that it's mild in the large scheme of things.


It took 30 years, but literate programming paradigm is now very commonly referenced when working with notebooks, even though it's not what Knuth was going for in the first place and the original intent was different.

Notebooks are a direct evolution of REPLs and at the time of writing (Knuth published his paper on literate programming in 1984), REPLs have been well known (they had been around since 1970s). Yet there is not a single mention of them in the original paper, nor any prediction how literate-programming-capable notebooks could look like. At the time, these were two different concepts each serving its own need (documentation vs exploratory programming).


I have been using ObservableHQ as my main environment for over a year. I have found that I have started adopting a "documentation driven development" methodology organically over time. The opening paragraph of the notebook, I review and edit almost every time I open the notebook. The opening paragraph provides a qualitative north star and tone setting for the whole project contained within. You can have a dynamic table to contents structuring the whole notebook. When documentation is included with source code, it is easy to skep between micro projects, as all information required to run the project and perform maintenance can be put where it makes most sense. So as a solo developer, I am able to scale more broadly than I used to be able to.

To me, literate programming is massive productivity left on the table. I love it.

I love it so much I developed https://webcode.run to extend observable to the backend with a fresh retake of a serverless compute environment. I have a working OAUTH server "hosted" here: https://observablehq.com/@endpointservices/auth if you are concerned this does not scale to interesting infra. You can eject source code as ES6 modules and publish to NPM which I have done for generally useful tools like the first REDIS client for web https://observablehq.com/@tomlarkworthy/redis


Literate programming is certainly a great idea. The problem starts when it is implemented badly in a project, and instead of code that is easier to read, the devs are faced with files stuffed with "comment-noise".

Should we try to write code that is primarily meant to be read? YES! Oh god yes! Code is read 1000x more than it is written. But that doesn't mean "stuff the code with comments".

Primarily, it means write code that is clear, concise, conveys the meaning, is logically structured, is not "optimized" into obscurity before even the first performance measurement has been done, and doesn't implement something just to fulfill some dogmatic paradigm.

Once that's in the bank, we can start using comments where they are needed: Explain Functions that are part of the interface, explain functions that have complex logic, explain imports that aren't part of the stdlib or well known 3rd party libraries. I don't need a comment telling me what `import requests´ is needed for. I certainly do need a comment telling me what the hell `from apputil.tools import tvectorization` does.


This "too many comments" argument comes up every time, and I say it's a straw man argument.

Apart from new programmers who put those comments in to remind themselves what each line does, I haven't seen "comment-noise" since... 2005 sounds right.

If it was common I'd be able to go on GitHub and have my searches spoiled by comments. But instead on GitHub it's the opposite, as it is in my working life: people have gone to a "we don't write comments" default position.

Getting a balance is necessary, but I'd rather we had programmers over-commenting than under-commenting, especially for open source projects where "read the code" is used to mean "we don't need documentation as our code's so great".

It also depends who is going to read the code. Other experienced programmers need different comments than when my code is going to be read by developers who are unfamiliar with the codebase and didn't expect to ever touch it.

I agree with what you say but I encourage people to add comments and then remove them later if not needed, rather than omit them entirely.


I don’t. I worked on a program with hundreds of instances of the comment “do this here.” Why not just preface every line with that?


> Why not just preface every line with that?

Because that's noise that makes reading the code more difficult. Creating a separate markup file that provides comments (even overlapping) that are associated to files and lines, for display in an IDE window seems like the logical way to go from the current style of inlining comments into code.


I have my doubts. It seems like that would be even more likely to drift than inline comments.


> It seems like that would be even more likely to drift than inline comments.

I'm not sure why that matters to utility.

If you don't check the documentation/comments and code in a changeset, you are going to get drift. This is regardless of the documentation methodology. How poorly developers maintain code is a constant in the case of drift (lack of rigor), so the method is irrelevant, except in how descriptive it can be.

A complex application is a nested logical structure tied to orthogonal business requirements. This is why READMEs and docs folders exist, alongside large block comments, hidden amongst the code. How to best describe code is definitely not to inline it, as that's too limiting for what is to be described.


Mostly agree, but I want to narrow down a few points:

Collectively, we already fail at the first stage, writing code, which is well structured and through its structure conveys meaning and understanding. It is rare, that I see this being done in a way, that makes me think: "Ah, great code!" Perhaps in SICP or in some conference talks I see it more often, when people know, they are going to present it and it should be done really well. When I see some typical algorithm video however, people seem to get a kick out of explaining comparatively unreadable code, instead of first structuring it really well. Structuring code really well is an art and certainly one cannot always do as great as some of the greats, who had years time to improve some snippet.

Code being read 1000x more than written might be an exaggeration, or over-generalization, as not all code will be written to last years, however, I agree with the basic idea it implies.

I do not mind there being long comments in the code. I have done so myself, as long as they are useful for an in-depth understanding. You might get into a situation, where you work with an API, which when the concept that is implemented behind it would make things clear, but you do not want to assume understanding of those concepts by the reader of your code. Of course you could say, that they must read the docs of those concepts then, but what if that is a paper, which takes days to read and understand yourself? Maybe you can put very helpful explanation in some comments, to safe others lots of time.


> I do not mind there being long comments in the code.

Me neither. If the code is good, its author may put in some Haikus about his thoughts about the meaning of life for all I care. If it's to much, I can use my IDE and just hide it.

What bothers me isn't lots of comments per-se, but when more emphasis is put on having lots of comments, than on having good, readable code.


Please, keep the haikus somewhere else.

Comments tend not to be maintained with the code. Block-comments up-source often describe general philosophy covering functions way down-source, that may or may not be relevant; does this one-line change on line 200 impact the philosophy comment on line 20? Who cares. Fix the bug, and let's go home.

So comments are needed only if the code is tricksy. Tricksy code is to be avoided; code should generally be clear. If it has to be tricksy, then a comment is justified to explain why it isn't clear (otherwise someone will come along and de-tricksify it in the interests of clarity).

Code is written for people, not for computers. Otherwise we'd be writing programs in hex (which is how I had to write my first-ever program).


> So comments are needed only if the code is tricksy

If the code in question is part of the pkg/module/crate/whatever's API, aka. someone else may have to use it some day, it should be commented regardless of how complex it is. Whatever interacts with "the outside world" should be labeled, that's true in code, and that's true with the big red emergency stop button found on heavy machinery. It doesn't matter how obvious the usage is.


> If the code in question is part of the pkg/module/crate/whatever's API, aka. someone else may have to use it some day, it should be commented regardless of how complex it is.

No, the API should be documented, which is orthogonal to the presence or absence of comments inside the code.


That depends entirely on the toolset available.

Languages like Java, Python or Golang enable inline documentation which can then be used by standardized tools (Javadoc, Docstrings, Godoc).

eg.

https://pkg.go.dev/flag@go1.17.6#PrintDefaults

The entire documentation of this function (including HTML) is generated by the Godoc tool reading comments which conform to a certain convention.

https://cs.opensource.google/go/go/+/refs/tags/go1.17.6:src/...

There is literally no downside to this. The code and its documentation are in the same file, its much simpler for developers to update it when there is a change, and its easy to generate documentation directly from source on the fly.


Agree. I suspect the difference of opinion might be down to systems like Javadoc, that transform code comments into API docs. But Javadoc comments are often auto-generated from function prototypes, which results in Javadoc comments that add nothing to the raw code except bloat. I don't think I'ver ever seen good documentation produced from doc-comments.

There's another thread on the front page about literate programming; I suspect that doc-comments amount to an effort to weave code and documentation together, in a knuthian manner, to kind-of automate literate programming. It doesn't work, AFAICS. You get bloated code and bad docs.


Love your comment.

doc-comments are good because it explains well what the class/method/whatever are there for, sometimes with examples, that helps understand the code.

But I hate that they use so much space inside the source code that I have to scroll hundreds of lines to read the code. And those doc-comments are filled with markup that makes it harder to read.

I would like a literate a system that generates source code that is well formatted and where the only comments are what the function does and some inner function comment where the code is complex. Every other lengthy and detailed description should be inside the produced pdf/html.


We need a programming language that only allows writing at the bottom of the code.

That way, the thoughts of the programmer become more linear, and the code becomes easier to read.


Yeah, the word "read" seems to have seduced us into an obviously false analogy. "Reading" code is not like "reading" a novel. Perhaps this could seem more plausible if your work was primarily along the lines of Knuth's, rather than combining the same bag of shopworn, well-known techniques into academically rather uninteresting production code.

Another article on this topic I recommend is "Code Is Not Literature," from Peter Siebel: https://gigamonkeys.com/code-reading/


Yeah, usually "reading code" doesn't mean reading a file from beginning to end, just reading (and, more importantly, understanding) the parts that you need to understand in order to fix a bug or implement a new feature. And "readability" also includes being able to easily find the places you need to "read" and then change.


I think Knuth is just plain wrong - documentation and code should be separate things with different aims and requirements.

It's one thing to include some background notes around a single algorithm, it's something else entirely to document an entire application.

The missing link is the lack of meta-representation and abstraction tools.

Text is very poor way to show relationships, and most code is more like a tree structure. IDEs have gotten better at representing this, but they still tend to show text files as primary and relationships as secondary.

More text in/around the text files is definitely not the answer. Structural representations - dynamic useful ones, not bureaucratic nonsense like UML - and smarter searching and filtering are more likely to be helpful, with associated text descriptions for key concepts.


I think you’re right, especially when you look at the kind of silliness he’s actually writing. It’s just overcommented code, and worse than that, trying to be cute. Sorry, massive respect for the guy and his accomplishments, but this is not a good idea.


I don't get the association between reading and literature. Math books can be read, are there Math book reading clubs? now take away all the context and just leave the equations, that would be code. I don't known about others but I need to actually do the math to understand a math book and those always include the context.


I’d say the notion of “curling up with a book” strongly evokes literature. And calling it “literate” programming invites the comparison as well. But a program isn’t like a math book either. It is actually pretty rare to actually just read a program start to finish.


I understand what Knuth means by literate programming and I personally like doing it for some pieces of my code so that a few months later I understand what I was thinking at the time and either continue or change things because I now know better. The same way one might write their mind in a journal and cringe at the content years later.

Seibel seems to have taken the literate part of literate-programming too literately with his reading club and not expecting to implement at least a toy version of the code to understand it.

This is why I compared it to a math book, in that it is also a book that is read but to really understand something one must work out some of the problems. I did not mean that a program is like a math book, I meant that a literate-program is like a math book because it adds some context to the math inside. I think a program itself is like math with no context where you have to reverse engineer what its doing by guessing what the person who wrote it was trying to do.


Well, I agree that it makes a lot of sense as a pedagogic tool. But I thought the claim was more expansive than that.


I would suggest it's just as rare to read a math book from start to finish...


Maybe so, but the progression and unit size are closer to narrative books than to code.


The author of this post has another on that topic: "Nobody's just reading your code" <http://akkartik.name/post/comprehension>


Code as it is written today is not literature, but Knuth's idea was to make it readable like literature, because you would be mostly reading words, formulas, and algorithm descriptions, and code itself would be an incidental detail.

Peter Siebel is right. However, he is talking about code as it is written today.


Right, but my contention here is that not achievable for reasons Siebel describes: a real program ends up having to have a bunch of stuff extraneous to the main idea most of the time.


A most interesting article, thank you.


I've read a fair number of Knuth's literate programs, and I've read this post a few times over the years, and my opinion is mixed.

The first point in the post is that many (non-Knuth) LP systems provide typesetting, but allowing arbitrary code re-ordering (as in Knuth's WEB/CWEB) is actually the more important feature. This is a good point.

But next it faults Knuth for putting #include statements at the top of the program, instead of using his system's reordering feature to have an "⟨includes⟩" section that is later filled in when each #include is needed (the way he does for "⟨Type definitions⟩" and "⟨Global variables⟩" in those examples). This slightly misunderstands the purpose of literate programming, I think.

As far as Knuth is concerned, the main idea in literate programming is an attitude, an orientation: treating programming as writing, for a human reader. (This particularly makes sense for him as he is primarily, in a very deep way, a writer.) Now, writing is always (best) done with a specific reader in mind: you assume the reader has a certain background/prerequisites: some things that don't need explaining, and some things that do. Depending on the reader you're targeting (e.g., yourself a few years from now), and how polished you're trying to make your presentation, you may well choose to take it for granted and not bother explaining that a C program will have some obvious #includes at the top. Take it for granted, as understood between friends.

From this point of view, as long as you're writing for a human reader, it is literate programming whether you're at the extreme of explaining every part of every line of code, to a reader who does not even know the language, or at the other extreme of "here's my program: ⟨the entire code of the program⟩": it's only a matter of degree, how much you want to explain. [This is actually one of the misconceptions about LP: it doesn't necessarily require you to write lots of comments, or to explain everything verbosely; the main thing is to treat the activity as writing, and explain to whatever degree it makes sense given your intended reader. In Knuth's own TeX program http://mirrors.ctan.org/info/knuth-pdf/tex/tex.pdf , the one he came up with literate programming for, there are sections where he explains things very thoroughly but see also section §315 where, after explaining, he says "This is easier to program than to explain", and §69 where he says "Readers who like puzzles might enjoy trying to figure out how this tricky code works; therefore no explanation will be given" :-)]

A few other criticisms in the post are understandable given unfamiliarity with some further context about Knuth's work and these programs:

• Many of the examples being criticized are from programs that Knuth wrote for himself, as he mentions at the top of https://cs.stanford.edu/~knuth/programs.html — obviously the standard of polish and degree of explaining will be different when something is written up for public presentation rather than for oneself. (As I said, these examples also show that to write and enjoy LP you don't need to overdo it, feel free to explain only as much as you want.)

• An example is criticized for the fact that "GraphBase data structures" are not described in-line in the program, but again, as the webpage says, this is a program that uses "the conventions and library of The Stanford GraphBase" — Knuth has published an entire book about these data structures, so it isn't necessary (or realistic) to re-describe them inline. In fact, many of Knuth's programs can be criticized for being monolithic rather than modular, but the Stanford GraphBase is a rare case of Knuth actually factoring out a library for use in many programs.

• There's a criticism for "a steep jump across several levels of abstraction", but, contrary to common opinion, Knuth has repeatedly mentioned this as a good thing:

> "I have felt for a long time that a talent for programming consists largely of the ability to switch readily from microscopic to macroscopic views of things, i.e., to change levels of abstraction fluently." — 1974, Structured Programming with Go To Statements (http://www.kohala.com/start/papers.others/knuth.dec74.html, https://pic.plover.com/knuth-GOTO.pdf#page=32 )

> "I believe the thing that marks a computer scientist most is an ability to jump across levels of abstraction: We see the small and the large and several things in between as a continuum, and we don't even notice that we're jumping levels. — 2013 (https://programmingphilosophy.org/posts/jumping-across-level...)

> "after decades of observation I’ve come to believe that one particular talent stands out among the world-class programmers I’ve known— namely, an ability to move effortlessly between different levels of abstraction […] A programmer must deal with high-level concepts related to a problem area, with low-level concepts related to basic steps of computation, and with numerous levels in between. We represent reality by creating structures that are composed of progressively simpler and simpler parts. We don’t only need to understand how those parts fit together; we also need to be able somehow to envision the whole show — to see everything in the large while seeing it simultaneously in the small and in the middle. Without blinking an eye, we need to understand why a major goal can be accomplished if we begin by increasing the contents of a lowly computer register by 1. The best way to enhance our level-jumping skills is to exercise them frequently." — Foreword to The MMIX Supplement, published 2015.

• The second major criticism in the post is that the woven program (the readable output of the LP system: a typeset PDF file or book or whatever) is "dead". This is reasonable given the majority of readers, including myself in the past. But in fact, the intention is the very opposite of "we can't make changes to it while we work on the codebase. Everybody reads a pdf about a program at most once, when they first encounter it." For Knuth, the printed/woven version is the program, the one he thinks of as canonical and the one he uses whenever debugging or making changes to the program (with pencil on paper, I think). It serves him well: here he is in 2021:

> While I was preparing this round of updates, I was overjoyed to see how well the philosophy of literate programming has facilitated everything. This multifaceted program [TeX] was written 40 years ago, yet I could still get back into TeX’s darkest corners without trouble, just by rereading [B] and using its index and mini-indexes!https://tug.org/tugboat/tb42-1/tb130knuth-tuneup21.pdf

And unlike the common criticism (not present in this post, to be fair) of the author's order not being what a reader may want, in fact a book is not meant to be read linearly. ("Books are not scrolls. […] Books are random access" — Manuel Blum, https://www.cs.cmu.edu/~mblum/research/pdf/grad.html) Knuth also recommends reading in whatever order makes sense; he thinks that his indexes are enough to enable this, though readers like us may disagree.

His idea of reading (a book, or a program) does include "poke at it and run what-if experiments", just that he does it in his head. See Peter Seibel's post at https://gigamonkeys.com/code-reading/ where he describes how Knuth was one of the only two interviewees in his Coders at Work who do a lot of reading other people's code, and he also includes some detail of how Knuth read some program. (See also this great post by Dylan Lederle-Ensign on Knuth's reading practice: http://www.dlederle.com/2018/11/25/reading-programs.html In fact, one of these programs Knuth "read" this way while spending a long time in hospital with a broken arm, he says somewhere IIRC.) So though I agree with the ideal of "viewing live, modifiable, interactive code", the intention of Knuth-style woven output is far from "passive reading" as the post says.

Nevertheless, given most readers today (probably this was true in 1980s too, to be fair), the post is probably right that given a typeset version people will read it passively, so overall this criticism is probably worth making, at least as a warning.

There are lots of other criticisms that can be made about how Knuth does literate programming (here's a discussion I found just now: https://wiki.c2.com/?LongFunctionsByDonaldKnuth), and I've made quite a few myself, but this comment is already too long (and too late). For the "ADVENT[URE]" program in particular, see this discussion from about a year ago: https://news.ycombinator.com/item?id=25597842


The examples give me the creeps. How can one obsess over readability of code (in form of comments) but not think about writing readable code?!

For example:

   cell\* run(string s) {
     stringstream in(s);
     return run(in);
   }
What is "s"?!

What is "in"?!

What the hell does "run" do?!

All this code commenting (which is bound to deviate from the implementation!) but no thought into naming and structuring code in an easy to understand fashion.

Replace "run()" with what it actually does even if it means naming the function "replaceAllNamesInTheInputStringByDoingAFancyComputation()" or whatever.

It's all better then "run()".

Replace the variables with easy to understand names!

I'd be mad as hell if my colleagues wrote that code.


That's pretty funny, thanks for the feedback on my style.

More on my worldview regarding readability: http://akkartik.name/post/readable-bad

In particular:

"In practice, a series of locally well-chosen names gradually end up in overall cacophony. A program with a small harmonious vocabulary of names consistently used is hugely effective regardless of whether its types and variables are visibly distinguished. To put it another way, the readability of a program can be hugely enhanced by the names it doesn't use."

You'll probably not like Smalltalk programs either, which almost entirely have names like `aString`. These days I don't get hung up on style. Everyone has their own style, and that's good. When I read a program that does something useful to me, I try to adopt its style.


I think that people who've been repeatedly exposed to C code written by people who gave everything not particularly well chosen one-letter names end up allergic to them just in general.

Given in this specific case the reader (of the article rather than the comment you're replying to) already has the context that it's an interpreter implementation, the meaning of run() was immediately obvious to me, but if I'd missed that sentence in the article I suspect I'd've reacted rather differently.


One theme of this post -- and my site in general -- is that blog posts are a poor use case to optimize code for. I'd love for you to `git clone` the repo mentioned here and try reading it with your usual editor and so on. That's the habitat my code is really intended for.


I have the wart repo cloned into ~/tmp now and am quite fascinated at my first look but haven't dug into it enough to form any actual opinions.

I might remember to email you later but if there's somewhere on IRC you're usually connected to telling me where that is will significantly increase the odds of my actually getting around to providing feedback if you'd like that.


<3

Yes please, I'm on akkartik on irc.esper.net, irc.libera.chat and irc.tilde.chat.

More options: http://akkartik.name/contact


Effort to refactor is something to consider, and verbosity makes this harder as you get lower level.

Another thing to consider is the very verbose names can be something that people actively avoid for clean code, and you could end up with spaghetti code to make it both readable and verbose (imagine someone doing this for years and then handing the code off to someone when they leave because they were so pigeonholed in that system that no one else could easily collaborate).

Downstream, you can end up with tech debt instead of optimally refactored code as people struggle to do something of a smaller scope. And if every tiny thing is unit tested, it gets even harder to change without expanding the scope of work.

Math code is an extreme example of where verbosity can quickly become detrimental.


Definitely have to disagree.

"Clean" code is often an excuse for lazy, short names which need a comment to explain them. Consider this completely fabricated example:

// calculates the area of a rectangle with sides a and b, u being true for metric, false for imperial

int rarea(int a, int b, bool u);

versus

int rectangle_area(int side_a, int side_b, bool metric);

which more or less doesnt even need a comment anymore.

"Clean code" people want short names, massive comments that can be collapsed, so that from really far away, it looks neat. Its a terrible beginner mistake I see time and time again, and it shouldnt be defended.

If you write a math formula, how is writing "side_a" or "phi" worse than "a" and "p"?


Why would it be worse? Why would you write p instead of phi?

I would, however, point out that to call this function, you need to separately compute lengths of sides. Where do those come from? How are coordinates stored? How are you wiring all this up? What happens when you change the data to support more dimensions, or to use memory pools, or to add new shapes?

To be clear, a lot of math needs a ton of complex code, and you also often have algorithms optimized for performance, approximation, numerical stability, etc. You can encode your understanding of what’s going on in the naming and break it into chunks so it’s readable, but that’s not necessarily a good thing.

Write some code to compute the intersecting earth-surface geometry of the projected frustum far planes from two satellites’ cameras at some point in time. Who’s using this code, is it a library? How do you abstract it? Who are the consumers of the code, will they ever change it or only use a part of it and want to refactor? Is it going to be a lot of work to refactor and are they just going to inject what they need into an evolving system of glue code? If I have to call it n times, how can I pass in and reuse memory to avoid memory penalties? What happens to names when we have to reuse symbols?

Edit: probably not obvious, but you can probably elegantly describe what you’d do with ideal inputs, but getting those inputs is the particularly hard part.


For an even more contrived example, see my other comment [1]

[1] https://news.ycombinator.com/item?id=29872191


“If you write a math formula, how is writing "side_a" or "phi" worse than "a" and "p"?”

When you pick up your pencil to do some manipulations on that formula, you’ll wish you’d chosen the one-letter variables. Also, single character variables, perhaps with different fonts, subscripts, or other decorations, make it easier to see the structure of the equations. That’s why we use them in math.

For those of us who use a pencil to manipulate our code, to see how it might be transformed, the value of short variable names is the same—we’re often going back and forth between code and math. That’s why languages like Julia that let you use Unicode identifiers are so cool.


`bool metric` does not ring any bells (I never lived in US, so I had to refer to the comment to understand it), and units of `a` and `b` and result are missing.


`bool metric` implies that `metric` can be YES or NO, that's clearer than what was there before.

Its a made-up example, this discussion is about style, not about semantics. To please those who would like to see a semantically improved version: (C++)

    enum class Unit {
        Meter,
        Inch,
        Foot,
        // ...
    };

    template<Unit U>
    struct UnitValue {
        double value;
    };

    template<Unit U>
    UnitValue<U> rectangle_area(UnitValue<U> side_a, UnitValue<U> side_b);


    // invocation example
    constexpr Unit unit = Unit::Meter;
    UnitValue<unit> result = rectangle_area<unit>({5.3}, {1.5});

    std::cout << result.value << std::endl;
There is still room for improvement, maybe some concepts to further generify it, some more comments, maybe a PDF with full documentation, ...


I'd imagine in an ideal world they'd've gone with `enum(Metric, Imperial) units` but as a direct rewrite of the original function signature it was probably the least worst available option.


I’m all for hyperlinks but I also think there’s something crucially valuable about presenting some kind of ordered, intelligible, linear narrative structure. The idea of organizing a program as a sequence of chapters is to me the core of literate programming. I do it by simply ordering my files with numbers. You can start reading at file 1, or you can skip the first chapters and jump to a more substantial chapter where the key concepts are already established… Feel free to start reading at the very end; this is also a good strategy for prose, see Adler’s “How To Read a Book.” Actually I think Adler’s framework would be very interesting to apply to programs. Literature isn’t just novels, it’s also monographs and anthologies and textbooks.


This seems like a totally unrealistic and impractical way of organizing a codebase. Living, useful codebases under active development need to be searchable and well tagged rather than laid out in "chapters" in my experience. In most scenarios it is also not a great use of time to try and read through a codebase line by line from A-Z.


It works for me, it’s the only way I like to write programs, so I don’t know how to respond to the notion that it’s totally impossible. It’s not perfect, it has its own difficulties just like any way of doing things.


To clarify, and I mentioned this already, having “chapters” doesn’t mean you need to read every line in order. If chapter 1 is “Basic Setup of the BCK387 Chip” then you might want to skip to chapter 5, “Drawing Lines to the Screen” if that’s what you’re interested in, knowing that you can return for the preliminary details if you want.

Within any single source file, I will assume that you take some care to arrange parts in some sensible order. Why is it such a ridiculous idea to do this for the files themselves?


Usually the order imposed on files is to group like things together (instance variables, public methods, private methods, something like this). That’s actually somewhat hostile to the notion of making something read well from start to finish.


> linear narrative structure

IMHO, this is one of the three fundamental mistakes of literate programming:

1. Not everyone is an author or a teacher like Knuth is. Writing is actually hard.

2. A program is obviously not a linear thing. It is a network of interactions. The only way you can make it linear is to tell the tale of what happens in a given set of circumstances.

3. Docs and comments are prone to bitrot. You absolutely want to minimize then.


Everything that anyone has ever written a book about is also a non-linear network of interactions. Consider a textbook in any field. Physics is not a linear thing. Economics is not a linear thing. Even the thing a novel describes is not linear: characters are not linear, the world is not linear. You can linearize a program randomly, alphabetically, chronologically, or more usefully by order of module dependency.


Yes. There is never one view of a program, but multiple architectural views.

Literate programming in the sense of documenting a program like a monograph is too one-dimensional.

I would like to see an emphasis on test inputs/outputs instead. Given all the Test vectors the actual code becomes irrelevant. It seems that we focus on the code because it used to be the bottleneck, plus it’s the densest piece of information in the system…and takes less paper to print.

In a world where modules must be constantly integrated, I’d rather read the test traces than the code itself.

Good programmers can simulate the code in their heads and internally generate the traces…but presumably the point of literate programming should be to remove that burden from the reader.


It is interesting to read this after reading about Pluto, the notebook system for Julia.

Pluto is exciting because - it provides nice text formatting easily via MarkDown and simple production of graphics - it allows you to build a data flow out of small understandable steps

and, unusually, - it understands the dependency graph between cells of computation. That means can rearrange them as you see fit but they will still execute in the right order. Moreover, it makes it so everybody sees the results of executing things in the right order. This is worlds different from most notebook systems

- you can embed HTML controls directly in your notebook so that you get reactive computation

This meets many of the goals Kartik was talking about in his blog.


I think a pragmatic compromise here is to do "chatty" programming.

Don't tell me tautological comments("add X and Y"), but I want to know what the overall strategy of your algorithm is. Is it flawed? Do you like it? What needs improving? Why did you choose this data structure over that one? It doesn't have to be much detail, but having a chunk of text to read is some of the best onboarding you can get for a new piece of code.

Code with comments that simply write down what it is supposed to do, is not documented properly. I can now see what it does in a documentation generator, yes, but the moment I need to work on that code it's just an opaque piece of crap.


Yeah, explaining “what” in comments is rarely helpful. For professionals, a certain degree of language-literacy should be assumed: `#include <string>` doesn’t need a comment about its semantics; it only wants a comment if it’s surprising that the containing file would need it. Here are two examples in the past week I’ve suggested changes on a PR to remove comments: 0. `case foo: // fallthrough` -> `case foo: [[fallthrough]];`. That won’t rot and doesn’t make the reader switch languages while reading. 1. `if (code < 1000) { // http codes are less than 1000` -> `constexpr auto maxHttpCode = 999; if (code <= maxHttpCode) {`. I find that even if it’s a local constant, having it named is the first step toward reuse.

My favorite: `void setTimeout(int time); // in milliseconds` -> `void setTimeout(std::chrono::milliseconds time);` which adds unit-safety and eschews comments that can rot.


Why compromise?! I never said LP is bad. I just said we can do it better than Knuth does it.

Knuth didn't take us to the promised land, but he did show us a path to it.


The compromise is between using full fat literate programming, and keeping your job (i.e. using it at scale)


Ah right :) I thought perhaps you were alluding to something I said.


Instead of criticizing Knuth or the critique, I will more constructively point at a pretty good example of literate programming: jonesforth. It's pretty simple to understand the complete system (in x86 assembly and forth itself). Highly recommended read, even if you are not a forth freak.

I'd love to know about similarly documented code bases. I have femtolisp on my bucket list.


I always found literate coffeescript to be pretty neat. https://github.com/jashkenas/coffeescript/blob/master/src/sc...



The whole of ferret's source code is in a single org-mode file, following the literate programming style: https://github.com/nakkaya/ferret/blob/master/ferret.org


Jonesforth is awesome. But it is not LP.

Semi-literate systems are not LP.

The essence of LP is to not be constrained by some language's compiler when deciding how to explain a system to another person.

Now, lots of languages today are nice enough that you can work within them and still do LP. So it is certainly possible for a program written in a semi-literate system to be LP. But that is a property of the _program_, not "Literate Haskell" or whatever.

Jonesforth is written in Assembly. Assembly is not nice enough to write a Literate program in directly.

It is certainly possible to write beautiful programs without LP. But I think LP is a valuable idea that we should at least understand the boundaries of. If you think Jonesforth is LP, you don't understand LP.


I personally use literate programming to maintain my "dotfiles", mainly NixOS [1], and I _love_ it. I like to describe all possible alternative tools, why I don't use them, possible tools that look nice, random ideas and blog posts that describe parts of my config, add TODOs and screenshots, ... in short everything that is really ugly to do inside source code comments. Also I gain structure; adding headings to a 3000 LOC config is very nice.

For tangling I use lmt [2], as it works with Markdown and also play nice with Emanote [3] (full syntax highlighting inside the code blocks.). That means all my "dotfiles" are inside my Zettelkasten [4] and can be navigated like any other note I have.

[1]: https://nixos.org/

[2]: https://github.com/driusan/lmt

[3]: https://github.com/srid/emanote

[4]: https://zettelkasten.de/


Related (and shameless plug): http://antirez.com/news/124


It occurs to me that a few places in my code effectively have 'backup comments', but invariably because I found a faster or otherwise apparently more desirable way to implement something and having the original version Right There both acts as an explanation of the current code and also reduces the effort required to back out to the previous implementation temporarily to double check parity.

I'm ... invariable a little ambivalent about doing that, but I do it rarely enough and the times I have have come in handy often enough later that I think it's one of those things where "ask yourself twice if you really want to do this, but don't never do it" applies.


I was thinking about all of this lately. Knuth's LP is useful when you present an algorithm. That's where the quantity of explanation text is much greater than the code itself. It is also good when you describe an algorithm because it is a dynamic thing : an algorithm has to be "processed" in your mind to be understood and a good explanations might help to describe its behaviour. A notebook is good when you're doing steps to achieve a goal; but it's not good to explain an full programs I think. What would be nice is to have notebooks woven into bigger codebases. Or it would be cool to have links from notebooks directly to code, functions, methods,... for example when you just want to mention you're using a part of your code that doesn't deserve to be explained by a notebook. Also, something that would be nice is to have hierarchical notebooks. On the first level, you have a general explanation of what you do. Then you can go deeper and get more information about details of your code...


On that note, I’m of the opinion that source code should by default be ordered from high level to low level, e.g. calling functions before called functions within any given source file. More generally, the source code order should generally be close to a topological sort of its dependency structure. Of course, the programming language has to accommodate that, and many don’t (C is particularly bad). That approach would also imply for includes/imports to come last in the source code. Unfortunately most (all?) programming languages require them to come first.


Observable is execution in topological sort order

https://observablehq.com/@observablehq/how-observable-runs

You can totally put imports at the bottom!


I disagree with the author's interpretation of comments after #include statements being "cruft".

For me the intent there was effectively addressing one of c's weaknesses, which is lack of namespaces and clear import statements.

It is the python equivalent of doing

  from module import fun1, fun2
instead of

  from module import *
such that when the reader encounters fun1 later in the code, a simple glance at the top of the file should make it abundantly clear where fun1 came from, saving you a wild goose chase grepping into your system libraries.

"Oh, but, I can use my super expensive IDE's code-hunting plugin to get a 'where is this defined' popup when I hover with the mouse for a couple of seconds bla bla bla" you might say.

True. But then you'd be missing the whole point of literate programming, and the actual spirit of "you should be able to read the code in bed like a novel", which the author seems to have taken a bit too literally.

To me, literate programming could be effectively reframed as "code that is so self explanatory and clear, even in the presence of complex external dependencies, that it could be easily fixed _in nano_ within seconds, without ever having to go hunting outside that file".

PS: Unrelated to the above rant, on the other hand, some people seem to think that literate programming == jupyter notebooks or more generally the whole "cram everything in a single file and comment heavily to create a report" style of coding. No. In fact, this is as far away from what literate programming is about in spirit as could possibly be.


Knuth is the author of "Art of Computer Programming".

He writes code mostly for instructional purposes, for humans to read, I would think.

Literate programming makes sense in that context. And it is true that to create a truly maintainable program you need to provide good documentation, maybe literate programming is the best way to accomplish that.

However I feel I don't have the time to invest in producing great documentation. I write code the best I can and my goal is to finish a big project in a timely manner. I feel documenting everything while the program is still evolving would be wasteful because then the documentation should be continually revised and typically it is not.

A similar issue applies to unit-testing. If I need to change my program I also need to rewrite the unit-tests. "Testability" is a great goal for software and so is "maintainability". But the most important quality of software is "does it do what it should, already this year".

If I wrote a blog-post or a technical article I would put great care into how I best present the software both in code and in documentation around it. But mostly I just write programs for the computer to execute, not for people to read about it.

The ultimate example of Literate Programming is writing a book about your program. I had a book once called "How Tomcat Works", about the web-server called "Tomcat". A great book explaining in detail how Tomcat does (did? )work. But who of us have the time to write a book?

My priorities are:

1. Write great code that does what it should

2. Write some API documentation about it so I and others can understand how to use it.

3. (The least important) Document and explain how the code works.


I love literal programming.

But I don't like the idea that the prose you read is out-of-order relative to the code you write.

I prefer a modern language that reads well in-order, that you can read as an outline. That way, you can use tree depth for significance.

Important stuff on the first or second level. Less important stuff under a sub heading, because it doesn't contribute to the narrative.


Knuth is obviously doing it wrong because his idea of "literate" programming involves chopping up the source code of a program at arbitrary line numbers, and assigning them macro identifiers.

What this lets him do is talk, in detail, about those fragments of a program in the academic paper that the program is about, with the commentary interspersed among those fragments.

Then the paper can present how those fragments are combined together to form the next unit, and so on.

It's a complete non-starter for production code; anything written by two or more programmers whose goal isn't to have a working system documented in detail by an academic paper from whose markup source code it pops out as a side effect.


This comment would make sense if Knuth used literate programming primarily for academic papers. But in fact he created WEB for writing TeX and METAFONT, both of which (while their source code was published as a book later) were production systems, and in fact for several decades now he uses CWEB for all programs he writes, including several a week that he writes for himself. (Some of which are online at https://cs.stanford.edu/~knuth/programs.html .) In contrast, apart from the paper he wrote introducing LP, and the two Bentley columns about LP in CACM, I'm not aware of any other academic paper of his that presents programs — at any rate, the total number must be very small.

The goal is not an "academic paper"; his experience (and that of others who have seriously tried LP) is that it helps with actual writing of programs, less time spent debugging, etc.

Yes, there are challenges with two or more programmers, but nothing unsurmountable. See "Literate Programming on a Team Project" (https://www.cs.princeton.edu/techreports/1991/302.pdf coauthored by Norman Ramsey, who later developed noweb) and some stories like https://news.ycombinator.com/item?id=17484452 (and https://github.com/thi-ng/geom which went from LP to conventional).


I have always been a little disappointed with both Knuths LP and TAOCP as if they are just missing the point. I feel that he has failed to raised software development from craftsmanship towards engineering. I do not know if he has ever seen this or that his approach has simply failed. Software engineering should be about going from the abstract to the concrete taking into within given bounds. Any implementation of an algorithm takes into account certain bounds. In the abstract things are infinite, in the concrete they are always finite. A program that can add two numbers, always puts restrictions on the size of those numbers and the way they are represented.


> I feel that he has failed to raised software development from craftsmanship towards engineering.

That's why he called it "The Art of Computer Programming", not "Engineering Principles for Computer Programming".


    The effect of this simple shift of emphasis can be so profound as to change one's whole approach to programming. Under the literate programming paradigm, the central activity of programming becomes that of conveying meaning to other intelligent beings rather than merely convincing the computer to behave in a particular way. It is the difference between performing and exposing a magic trick.
--Ross Williams


    Another thing I've been enjoying lately is literate programming. Amazingly it turns out to be faster to write a literate program than an ordinary program because debugging takes almost no time.
--Bill Hart


Id like to add that the author in the article does `using namespace std;` in his C++, entirely defeating the point of writing readable code. Whats eof()? Is it an istream function? Your own? C library?


For some people “i” and “j” are a better name than “currentStepInOuterLoop” or whatever other verbose names. I think it really depends on context, though


That's not what this article is about, or literate programming as a concept. Literate programming is mixing prose and code with a greater emphasis on prose. The good tools (including Knuth's) permit the reordering of code regardless of how the compiler may need to see it so that attention can be brought to parts of the code at the appropriate time (in the prose).


I've been using literate-programming for a decade now to do my side web projects (using a tool I wrote myself, of course, as tradition dictates in the LP space: https://github.com/jostylr/literate-programming ). I find it really useful as a project management tool in dealing with the chaos of several different languages, build tools, etc. With HTML, I've used markdown for text content, pug for more structural setups, plain HTML for header boilerplate, all weaved into the same output document that is just plain HTML. It also is often useful for splitting css between a main site sheet vs local to a file (e.g., a lot of css styling on the main landing page is different than the other content-based pages of a site, at least back when websites had content).

I've also found it useful when essentially plugging in data or HTML fragments into JavaScript. I can quickly write a trivial dsl that takes in the data in a convenient form and transforms it into a convenient code version. A variant of the transformational technique is when similar code is almost the same, but just needs a little subbing. I can write a single block covering most of the commonality, and then sub in the differences. For example, if writing some arithmetic operator function code, replacing '+' with the other operators.

An aspect I also love about the approach is having two views of the code. The LP view is more of an outline with blocks subbing in, with some transformations going on. The compiled LP version is one where you can see all the code in full context, minimizing the jumping around at that level, something that cannot be achieved with a bunch of functional calls.

Over the last few weeks, I have started to learn Elixir. I have noticed that I feel less drawn to use literate-programming for that language. It feels like it is so well designed that literate-programming is almost redundant for it. I am trying to figure out justifying that statement, but it is still early days for me in that language. But it feels like, for instance, the matching on function parameters so that one can avoid if-else if-...-else constructs cuts down on a lot of boilerplate stuff. Admittedly, JavaScript has a lot less of that as well nowadays with all the new language constructs. Maybe it is just about Elixir being a functional language with the safety and little overhead of calling functions that make it more attractive to use functions as the outlining / reordering mechanism. Also, the pipe operator allows steps of transformations to be done easily and clearly, which is super helpful. And having tests run from the doc code is something to embrace, further making the separate commenting in LP less favorable in the Elixir context.

One huge downside of LP is that it allows the dictates of a given language to be worked around, making it harder for others to follow up with the work. Ideally the text of the LP helps with that, but it still is a barrier. This seems less of a problem in the front-end web world because that is just a mess of competing notions, but in something cohesively designed with sensible standards that the community follows (my impression of Elixir), it would become a much bigger downside to strike out on your own path.


The presumptious, click-baity title turned me away from this article.


It does make me flinch every time it shows up on the HN frontpage. I wrote it at a time when I cared about getting on the HN frontpage.

That said, though, I do still believe it and stand by it in all particulars.

We'll see how I feel in 5 more years..


Hmm to nitpick a bit, people put includes/imports at the top because in most programming languages you have to have the import before using whatever you imported. This way you can use your imported stuff anywhere in the source file without worrying about relocating it when you change code.


But literate programming permits reordering code. The tangle step is where code gets ordered into a structure suitable for the compiler. With most literate programming tools (my experience is with org and org-babel) you can do something like this (a lot of stuff elided for space):

  # Chapter 1: The program
  The main function is a simple infinite loop, repeatedly performing gather,
  filter, and report tasks.
  #+NAME: main
  #+BEGIN_SRC c :tangle src/main.c :noweb yes
    <<main_includes>>
    int main() {
      while(true) {
        gather();
        filter();
        report();
      }
    }
  #END_SRC
  ...
  # Appendix A:
  #+NAME: main_includes
  #+BEGIN_SRC c
    #include<gather.h>
    #include<filter.h>
    #include<report.h>
  #+END_SRC
The reason to separate them is when the includes communicate nothing of great importance at a particular point in time. The structure of main, as trivial as it is, is probably more important to the reader than the includes. And since cross references and hyperlinks can be attached to the code block, it's straightforward to find where the noweb reference is. The same thing can be used to pull various other pieces out so that the presentation makes more sense (the output is meant to be read, so order becomes important in what and how you communicate).

Using a sketch of a numerical simulation program, you may have this code structure:

  -includes
  -parse data
  -simulation loop
  -report result
For reasons the parsing happens first. But that may be the least interesting bit (the data is just be a straightforward series of lines each containing some number of space separated floating point numbers). Why present it earlier when the simulation loop is really the heart of the program? Or in the simulation, the complex bit may be some novel numerical algorithm at the core, and the rest is bog standard loops and data munging. Pull that algorithm to the front, and then insert it (using something like a noweb reference) in the body of the loop.

  In Foo22 a new algorithm is described, we have implemented it here:
  .. code and prose
  With this completed we can now write the simulation loop:
  .. loop code people have seen 1000s of times before,
      with a reference to the algorithm above




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: