15 ms·
Literate programming: Knuth is doing it wrong (2014)
- jostylr 5y agoI'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 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.
- mgraczyk 5y agoI 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.
- rustnote 5y agoPeople 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.
- deleted 5y ago[deleted]
- iddan 5y agoThat 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 https://github.com/microsoft/vscode-nodebook
- fault1 5y agohow about https://github.com/winnekes/itypescript 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.
- jackosdev 5y agoYeah 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.
- deleted 5y ago[deleted]
- emodendroket 5y agoYeah, 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/ https://gigamonkeys.com/code-reading/
- copperx 5y agoCode 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 5y agoRight, 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.
- rob74 5y agoYeah, 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 5y agoI 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.
- max-ibel 5y agoInstead 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.
- troutmskreplica 5y agolcc: https://en.wikipedia.org/wiki/LCC_(compiler) https://en.wikipedia.org/wiki/LCC_(compiler) PBRT: https://www.pbrt.org https://www.pbrt.org
- rapind 5y agoI always found literate coffeescript to be pretty neat. https://github.com/jashkenas/coffeescript/blob/master/src/scope.litcoffee https://github.com/jashkenas/coffeescript/blob/master/src/sc...
- miguelsm 5y agoThe 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 https://github.com/nakkaya/ferret/blob/master/ferret.org
- akkartik 5y agoJonesforth 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.
- deleted 5y ago[deleted]
- Equiet 5y agoIt 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).
- mbrock 5y agoI’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 5y agoThis 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 5y agoIt 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 5y agoTo 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?
- daly 5y agoYou 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) 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 https://www.youtube.com/watch?v=Av0PQDVTP4A). I find literate programming to be the MOST important change in programming since I started in 1970.
- nesarkvechnep 5y agoIsn’t the tangle-untangle process fundamental in literate programming?
- kryptiskt 5y agoI 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 5y agoLisp 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.
- teodorlu 5y agoI 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.
- buster 5y agoThe 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.
- engmgrmgr 5y agoEffort 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 5y agoDefinitely 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"?
- ted_dunning 5y agoIt 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.
- wiz21c 5y agoI 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...
- deleted 5y ago[deleted]
- tlarkworthy 5y agoI 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 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 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 https://observablehq.com/@tomlarkworthy/redis
- antirez 5y agoRelated (and shameless plug): http://antirez.com/news/124 http://antirez.com/news/124
- mst 5y agoIt 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.
- lionkor 5y agoId 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?
- usrbinbash 5y agoLiterate 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.
- zelphirkalt 5y agoMostly 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.
- gypsyharlot 5y agoThe presumptious, click-baity title turned me away from this article.
- akkartik 5y agoIt 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..
- nottorp 5y agoHmm 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.
- Jtsummers 5y agoBut 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
- fjfaase 5y agoI 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.
- amelius 5y ago> 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".
- mhh__ 5y agoI 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.
- akkartik 5y agoWhy 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.
- BenFrantzDale 5y agoYeah, 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.
- daly 5y agoYet 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
- akkartik 5y agoThanks 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 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.
- alisonkisk 5y ago
- daly 5y agoAnother 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
- daly 5y agoThe 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
- daly 5y agoOnce 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 5y agoMy 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 5y agoThat 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 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 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.
- layer8 5y agoOn 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.
- tlarkworthy 5y agoObservable is execution in topological sort order https://observablehq.com/@observablehq/how-observable-runs https://observablehq.com/@observablehq/how-observable-runs You can totally put imports at the bottom!
- joshribakoff 5y agoFor some people “i” and “j” are a better name than “currentStepInOuterLoop” or whatever other verbose names. I think it really depends on context, though
- Jtsummers 5y agoThat'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).
- altgans 5y agoI 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/ https://nixos.org/ [2]: https://github.com/driusan/lmt https://github.com/driusan/lmt [3]: https://github.com/srid/emanote https://github.com/srid/emanote [4]: https://zettelkasten.de/ https://zettelkasten.de/
- tpoacher 5y agoI 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.
- svat 5y agoI'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 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 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 http://www.kohala.com/start/papers.others/knuth.dec74.html, https://pic.plover.com/knuth-GOTO.pdf#page=32 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-levels-of-abstraction 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 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 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/ 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 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 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 https://news.ycombinator.com/item?id=25597842
- galaxyLogic 5y agoKnuth 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.
- kazinator 5y agoKnuth 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.
- svat 5y agoThis 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 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 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 https://news.ycombinator.com/item?id=17484452 (and https://github.com/thi-ng/geom https://github.com/thi-ng/geom which went from LP to conventional).