That said, I wouldn't personally want to try and collaborate on such a program with more than one other person. It would make for a great single-contributer OSS library though. Rubber duck debugging built right into the prose.
My personal bet is that it is probably easier to collaborate on something like this than you would think. The imposed structure of programs, in general, already makes a lot of collaboration tough.
You can also find older, physical editions on EBay for $10-$15.
This works well for people who are writers by nature (like Knuth who's always making edits and improvements to his books https://news.ycombinator.com/item?id=30149221). One problem though (and there are several) is that because this is so personal, nearly everyone who seriously tries LP ends up writing their own LP tool (including the author of this post!).
There's also "nbdev" (https://github.com/fastai/nbdev) which seems like it should be the best of both worlds, but I couldn't quite get it to work.
- I needed a stable implemention as soon as possible, I had a performance issued that needed to be solved by range queries.
- The radix tree was full of corner cases.
So I resorted to literate programming, which is in general very near to my usual programming style. You can find it in the rax.c file inside the Redis source code, as you can see as the algorithm is enunciated, the corresponding code is inplenented.
Other than that I wrote a very extensive fuzzer for the implementation. Result: after the initial development I don't think it was never targeted by serious bugs, and now the implementation is very easy to modify if needed.
1. Lack of tooling.
2. Refactoring becomes nontrivial
3. How one would write a program in literate style will vary widely from person to person. If you write your code in literate style, it may be easy for you to follow it years later and modify it, but it likely will not be the case for a coworker. If they have to modify the code, the cognitive load will not be too different from that of just dealing with well written code.
Disclaimer: I've written two nontrivial programs literate style that I continue to rely on and occasionally modify years after writing them. It works as advertised.
In other words, the style varying between people is not a problem - bad writing is. And, unfortunately, in my experience very few programmers are capable of consciously producing good writing. The fact that most of the docs out there are barely-legible trash is a proof of this.
I'm sure that reading literate code from Charles Stross would be a blast. It would be exciting, sometimes surprising, but still clear, easy to navigate, structured in a way allowing for extension within a well thought-out framework. Unfortunately, when people without his talent try to use LP, they produce things on par with that unfinished fantasy novel you started writing in 8th grade.
Programming requires a bit of talent, but you can get by with lots of hard work. Literate programming is much harder than that and requires a lot of talent to be beneficial to the codebase. Without that, your LP code will be Fifty Shades of Twilight, and honestly, we don't need more of things like that.
And while you add to point 3, it wasn't my main point.
Take any two exceptionally good writers who have very different styles. If one of them produces literate code, the other may be able to understand it very well, but it is unlikely that he can modify it, along with the prose, and maintain the quality of the literate document.
It's not just about bad writers, but incompatibly good ones.
As someone experienced in the topic, What's the biggest hurdle when trying to refactor the code?
It's simply more work - but that "more work" is vitally important, tedious, and resistant to any kind of automated help.
I'm trying to find a way to describe this a bit better than the above. I think the easiest way to think about it, is that in most software projects you have a separate document that is the general architecture of the software. It is rare that you will need or want to refactor the architecture, so you try to keep that somewhat faithful to what the code is doing. In literate software, that high level architecture view is part of how you organize the code.
I'd guess it's updating cross-references in prose and rewriting chapters of documentation which no longer make sense after your refactoring.
I use one .org file to declare all of my configurations, and tangle them together into the aforementioned files. This keeps things pretty portable, and makes up for the unintuitive readability of many dotfiles.
It can also work for rudimentary shell scripts and other single-file goodies; however, scaling it to proper multi-file programs proves to be difficult, especially when multiple developers are involved.
- Smalltalk-ish things like writing suites of custom viewers for various types, - demos and examples in-line inside of a library - multiple stories about the same piece of code, but all with the ability to IMPORT the story as a library
I've been writing sicmutils[0] as a "literate library"; see the automatic differentiation implementation as an example[1].
A talk I gave yesterday at ELS[2] demos a much more powerful host that uses Nextjournal's Clerk[3] to power physics animations, TeX rendering etc, but all derived from a piece of Clojure source that you can pull in as a library, ignoring all of these presentation effects.
Code should perform itself, and it would be great if when people thought "LP" they imagined the full range of media through which that performance could happen.
[0] sicmutils: https://github.com/sicmutils/sicmutils
[1] autodiff namespace: https://github.com/sicmutils/sicmutils/blob/main/src/sicmuti...
[2] Talk code: https://github.com/sritchie/programming-2022
[3] Clerk: https://github.com/nextjournal/clerk
There is no reason to believe they are any more likely to keep code self-documenting (or to succeed even if they try) - it is not as if it will not compile or run unless it is.
I see literate programming to be an attempt to put some rigor into the otherwise terminally vague concept of self-documenting code (conceptually, it is way beyond the platitudes in 'clean code', even though it came first.) It is, however, doomed to failure in practice because it always takes less information (and less skill) to merely specify what a program will do than it does to not only specify what it will do but also explain and justify that as a correct and efficient solution to a problem that matters.
Neither 'literate' nor 'self-documenting' code are objective concepts.
Additionally, bugs can be fixed in-situ, refactoring can occur at will, and neither would require the prose around them to change, since code being talked about (despite moving or undergoing small changes) still fulfills the original, documented, purpose.
Imagine a multi-person project where every little feature gets its own file, and now the programmer has to find the source of the bug between interacting blocks of in code fragments split across multiple files, ehich are combined together by tooling.... oh wait, I think that describes just about any sufficiently large C or C++ project.
My role of thumb is that if it's not obvious why that particular line is there and removing it would break functionality, add a comment.
But I recently discovered that Google's zx [1] scripting utility supports executing scripts in markdown documents and I combined it with httpie [2] and usql [3] for a bit of quick and dirty automation testing and api verification code and it worked out pretty well.
I imagine for most people nowadays jupyter or vscode notebooks are the closest it comes to practical literate programming.
[1] https://github.com/google/zx#markdown-scripts
It works well for personal stuff where you would like to leave some bits of information for yourself (typically, configuration files).
It works well for small libraries where good documentation is important.
It works well for visualisation-work, where you may combine multiple languages and data-formats without writing API's for each.
In larger scale apps though and with collaboration; you run into problems with tooling on multiple levels. I am working on tackling scale, but collaboration is tricky. Mostly because you need structure to collaborate and then you will likely end up with an outline that's pretty close to a directory-tree and then you've lost one of the good bits of literate code in my opinion.
See https://observablehq.com/@observablehq/a-taste-of-observable... as a quick overview.
To plug my own work, I have written https://observablehq.com/@mjbo/genre-map-explorer-for-spotif... in a literate style, and many of the Observable community are similar adherents to literate programming.
Codesandbox and Google Colab come close but they still feel like tanks. I can code something up on my phone with Observable while waiting in line at the DMV...
I confess I'd rather forgotten what literate specifically meant beyond code comments describing the flow, but i did find it to be a remarkably comprehensive & understandable document, a prime example of how we might teach & understand computing. Even if it did leave me puzzling out what a number of the many many many scripts were for!
Certainly the overall project of computing needs a lot of help, ways to explain itself. Ive seen tons and tons and tons of "dotfiles" projects, but none have gotten anywhere near to as comprehensible as this literate programming project, from what I've seen.
[1] https://tess.oconnor.cx/config/hobercfg.html https://news.ycombinator.com/item?id=30748033 (19 points, 1d ago, 0 comments)
First, in a printed book, it is easier to find a previous page and compare a fragment on it with the current fragment. Second, a printed book has no links tempting you with the words "CLICK ME" to disrupt the flow so you can read it from cover to cover with fewer distractions. Third, anecdotally, I can see flaws much easier on a printout than on screen, both in programs and in texts.
This is why I like plain text for everything (or Emacs Org Mode) because then I can have multiple frames showing different parts of the same buffer in Emacs.
I still think of that today when I’m writing complex algorithms. I write everything first in prose. Then translate that to a more list like structure. And then I fill in the code around that.
Works really well when I have to come back months later and figure out what I was thinking.
Also are there any IDE plugins or error stack trace/debuggers for literate programming?
I haven't really paid attention to literate programming in a long long time and I'm curious if the field has advanced.
(Also I don't understand this: "A typical literate file produces many source files." Why? Why would you care about having multiple source files? Isn't the literate file the source at this point?)
Syntax highlighting? Good luck! But possibly you could work around this, e.g. via custom highlighting syntax. Same with any auto-complete, contextual IDE help, etc. Refactoring was painful.
Also, the text absolutely destroys being able to scan and reason about the control flow quickly. Especially bad when a dev decides something needs "a lot of documentation" and writes a small novel.
Needless to say, it was truly awful.
I wrote a PoC of a tangling tool that worked through "virtual" files and had a syntax aware handler, there's enough information that it could possible work with language servers too, but sadly I haven't had time to take things further: https://gitlab.com/lusher/tanglemd
Leo avoids this by keeping a "shadow copy" of the artifacts or annotations and doing a diff on detangling between all 3 versions.
Good question!
I always think it would be nice to just write Markdown sprinkled with code, but without IDE/editor support, it's dead in the water :(
So for literate programming, if you just think it is how you write the code (e.g. self-documenting or not), or you think it is the amount of commenting (e.g. doc string or not), if you are not first and constantly thinking about how to structure your code and establish context, you are not getting literate programming.
Now, once you understand your ends, the means (tangle or weave), will come along. It is easy to invent one if you don't have one. On the other hand, getting your coworkers to agree and work together, that's hard. It is easy to get machines to work together and it is easy for human to cope.
An interface with all its details is an end product. With literate programming, that shouldn't show up as a single piece up-front. It should be developed with layers, each layer with its context (why, what, how), each layer with design and implementation.
Certainly we still want a view of complete interface with all its details in one place. This is the same as the compiler still wants the entire code in its expected structure and order. That's the job of tangle.
If you are wanting to just do cweb, then the debugging symbols already let you step through the source line by line without having to look at the tangled source.
If you actually use noweb and desire autocompletion or type reminders (or really anything an IDE does), then functionally it cannot. Literate programming (and noweb) is great for configs, but as set up it simply doesn't work right for real programming.
Very very few people can start from the abstraction and get TO a literate outcome without a lot of false steps along the way.
Or, as an alternative, the LOC of a literate program has to include the 100x cost of exploring how to carve it out of the block of mud we start from, including making our own tools.
"Reader, she married him" as the first words of the book, not the last basically.
But don't writers face the same issue with their text? Am I the only one who writes more code than what ends up in a PR? Isn't that exactly what the Git history is for?
source code: https://github.com/gdeer81/marginalia example: http://gdeer81.github.io/marginalia/
Now, seen actual overall software quality (far less hacky than the past, but also unable to innovate, bloated, with gazillions of deps) we need to change back to days of the real innovation BUT that means we need to completely erase actual economical model centered on giants, witch can be "a little bit" difficult since they are giants and they do not like the idea to be thrown out of the window...
Sometimes when you are writing an article it may make sense to write LP-like snippets of code like
int my_function() {
// Initialize variables
return 0;
}
but you don't really need to invent the whole "literate programming" concept to do this and you don't need to write all of your code like that.Maybe I saw bad examples though.
On a side note, it seems a lot of the other commenters miss one of the best "features" of LP - minimizing repetition. Chunks of code can be reused and so patterns can become clearly evident. Also, chunks can be defined "out-of-order".
I stopped using 1 letter variables, abbreviations, and non descriptive function names. If a function or block can't be read and followed like a story, and without comments, it probably can be simplified.
"Literate programming" is a non-invention by somebody (Knuth), who is very much revered by many programmers (many of whom never even actually read him), but who was — let's admit it — just terrible at writing readable code. I'm very much not a fan of the "Clean Code" by Martin, but he had a very nice example of refactoring some of Knuth's code to show you what I mean (although, it's kind of evident that writing clearly wasn't in Knuth's DNA just by reading his famous books). Today, this is an attempt to solve a problem, which you created yourself by avoiding using tools that already exist to solve that problem. Then you invent all sorts of tooling and mental tricks to make solving this problem your way more comfortable. But if you would just use these already existing tools, there would be no need in making up a new name for what you do. It wouldn't be some "literate programming", it would be just programming, the sane way.
First off, what tools I'm talking about: well, that's everything PL developers invented over the decades, and it obviously depends on which PL you are going to use. If this is some pseudo-assembly language like what Knuth uses in his TAOCP, then, well, there aren't many such tools, so creating your own template-preprocessor (which is, in a sense, making a new PL with additional features on top of your pseudo-assembly) perhaps would be an okay-ish idea. But if you use something that people actually use for programming, then you surely have functions, some kind of advanced data structures, perhaps classes and inheritance, perhaps some templating features as well (like… traits?).
Going back to the example at hand (the code author "simplifies"): all that "simplifying" consists of a top-down description of what he's going to do. Really, the code he ends up with (in "transpiled" form) isn't that much harder to read and understand than his "LP" version of it. Inline some comments to explain what he explains in the "LP" version, and you and up with the same thing, but much more concise (so, faster to read — and easier to edit!). If it was a bit more complicated: you do the same thing that he did with his "templating", but simply by doing what programmers actually do in such cases — extract complicated fragments of a function into smaller functions, and give them proper names. Maybe add some comments — yes, they are a part of your PL for a reason.
Moreover, the most complicated thing in his example isn't how the algorithm is written down, but the very algorithm itself. It is ok as long as you never actually run this code, but if you actually use it in some useful program, where it can cause problems, a programmer coming across this thing would need to stop to wrap his head around what this is doing, if it's actually all subsets and how fast the call stack may grow (as it so often turns out when you use recursion to write down "an elegant" solution). I mean, I'm only suggesting, but wouldn't this be a little bit more straight-forward?
function subsets(elements) {
results = []
// All subsets of a set of 5 elements are basically binary numbers
// from 00000 to 11111, which is from 0 to 2⁵-1
for (i in range(0, 2^(len(elements)) - 1) {
results.add(get_subset_by_binary_number(elements, i)))
}
return results
}
// Blah-blah
// Given [1, 2, 3, 4] and a number with binary representation 0101
// will return [2, 4]
function get_subset_by_binary_number(set, number) { ... }
This isn't my main point, though. My main point is, that people write code for a reason. There can be number of reasons, but usually it fits into a range from "doing some enerprisy-boilerplaity stuff I'll need to redo over again next week" and "writing a book, which has code, because it's about programming, and code describes programming better than english". In the first case it probably won't need a lot of "LP-kind of explanations", and where it needs to go over "why the fuck did I do it like that" a bit more extensively, you'll just link Jira issue in a comment. In the second case it might look a bit moe like LP, but it's just called "writing a book".In all of the cases in between you'll add some amount of comments, always trying to minimize overall amount of stuff other people will have to read (and, well, you to write), which is expressing as much as you can with words you cannot avoid to write (i.e., code that actually does things, explaining them both to humans and to a computer) and minimizing what you can avoid (i.e. English). (Closer to "a book" on this spectrum it will also include some Jupyter Notebooks.)
> it's kind of evident that writing clearly wasn't in Knuth's DNA just by reading his famous books
— my experience, from reading (parts of) several of Knuth's books and papers, is the very opposite: Knuth is one of the finest writers, and writing is clearly in Knuth's DNA — even among the many hats he has worn (mathematician, programmer, computer scientist, teacher), at heart of everything is writing. (His Selected Papers on Fun and Games includes some stuff he wrote in high school and college; even those show his spirit.)
IMO, every page of his is a delight to read. I think the issue for those who find it otherwise may be that he writes in a very personal way (his personality shines through), and for those who are looking for something bland or generic, this can be a surprise.
Then again, this may be one of the chief problems with literate programming in general (why it works so well with one author, and doesn't seem to have had much success with a large team): writing is very personal, and for many-person codebases something "generic" may in fact work better.
Unfortunately, machines have a different way of understanding code than humans.