Get the top HN stories in your inbox every day.
mgraczyk
zelphirkalt
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.
leephillips
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.htmlRoyi
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.
ogogmad
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.
zelphirkalt
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.
User23
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.
R0flcopt3r
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.
brudgers
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.
funklute
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.
dangom
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
sdenton4
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.
zelphirkalt
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.
funklute
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.
jonnycomputer
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.
protoduction
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).
mgraczyk
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.
zelphirkalt
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.
huijzer
> (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
musingsole
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.
hiptobecubic
I lot of these rebuttals are "I can't imagine your use case so it's probably bad/weird/not worth it."
rustnote
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.
undefined
iddan
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
jackosdev
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.
fault1
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.
nefitty
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!
mbo
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.
d0mine
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.
hzhou321
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.
sdfgsdf
> 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.
leephillips
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/fault1
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).
eigenspace
You are of course correct, but 'the Julia one' is probably actually Pluto, since it's Julia only.
tpoacher
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.
Zababa
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.
daly
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.
argiopetech
+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!
daly
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.
cloogshicer
While you're editing code (which is most of the time), aren't you still reading and working with the non-typeset version?
daly
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.
cloogshicer
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.
nesarkvechnep
Isn’t the tangle-untangle process fundamental in literate programming?
daly
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.)
kryptiskt
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.
daly
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.
rustybolt
Newsflash: a PDF viewer is not a programming IDE.
andreareina
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.
rustybolt
I see that as a separate text editor and pdf viewer but I guess it blurs the lines a bit.
daly
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.
emodendroket
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.
akkartik
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.
Someone
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.
strictfp
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.
emodendroket
I wouldn’t support a no-comment policy, but I’ve wasted a lot of time thanks to misleading comments.
yissp
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".
User23
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.
secondaryacct
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.
hiptobecubic
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.
daly
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, Donaldakkartik
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.
Equiet
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).
tlarkworthy
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
usrbinbash
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.
pbowyer
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.
emodendroket
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?
Supermancho
> 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.
zelphirkalt
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.
usrbinbash
> 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.
denton-scratch
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).
amelius
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.
emodendroket
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/
rob74
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.
TheOtherHobbes
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.
emodendroket
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.
wudangmonk
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.
emodendroket
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.
wudangmonk
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.
couchand
I would suggest it's just as rare to read a math book from start to finish...
cxr
The author of this post has another on that topic: "Nobody's just reading your code" <http://akkartik.name/post/comprehension>
copperx
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.
emodendroket
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.
cafard
A most interesting article, thank you.
svat
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
buster
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.
akkartik
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.
mst
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.
akkartik
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.
engmgrmgr
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.
lionkor
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"?
engmgrmgr
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.
leephillips
“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.
dottedmag
`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.
undefined
mbrock
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.
Areading314
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.
mbrock
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.
mbrock
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?
astrobe_
> 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.
mbrock
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.
hasmanean
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.
ted_dunning
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.
Get the top HN stories in your inbox every day.
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.