This is right next to Leadenhall Market, which dates from the 14th century. I wonder if there were other markets on the same-ish site between the Roman forum and Leadenhall.
Xerox PARC used ASCII 1963 (for reasons I have yet to unearth) so its programming languages Mesa and SmallTalk used ← for assignment and camelCase to separate words in identifiers. This stylistic quirk was carried over to later object-oriented programming languages.
There are at least two aspects to literate programming.
Knuth wrote TeX in Pascal, which has a couple of limitations that annoyed him:
- Pascal has no module system: you can’t break a program up into multiple files
- The order of presentation of a program is dictated by the needs of a single-pass compiler
The tooling that supports literate programming is mostly about overcoming these problems with Pascal. (Knuth also uses it to split up larger procedures in a manner that makes me think of very un-structured C macros.) Most other languages are more flexible so they have much less need for a technology like tangle/weave.
The opposite extreme is Literate Haskell, which doesn’t need any preprocessor. You just feed your TeX document to ghc and it picks the code out of the \begin{code} blocks. Or instead of Knuth style you can write in Bird style, where the file is a plain text document with code marked by > quote blocks.
The other aspect of literate programming is organizing the code like a book. This is very difficult to do well. When I think of how I approach a codebase, it reminds me of textbooks that have a complicated diagram describing multiple suggested orders of reading (a chapter per module?), or the informal introduction to Algol 68 with its orthogonal table of contents. Ideally a program’s commentary should span both the explanatory and reference documentation quadrants.
What strikes me about Knuth’s literate programming is how hypertextual it is, albeit using printed cross-references and indexes. It seems to be in desperate need of interactive pixels. And an underlying programming language that has better support for Knuth’s preferred size of code fragment.
On balance I think modern tools achieve Knuth’s goals better than tangle/weave. Modern languages let you organize code much closer to its narrative structure, without WEB’s unhygienic scoping. Editors and IDEs and documentation browsers give you hyperlinks everywhere.
It’s then “just” a matter of writing the exposition. Less literate programming, more literate programmer.
But let's take for example JupyterNoteBook. It's nice but not as good Knuth's stuff. Why ? Because the notebook forces you to follow the evaluation order whereas Knuth allows you follow your ideas order. Now for the rest, Jupyter is simply much better. But fundamentally, JNB can't match Knuth's LP flexibility.
So I'd say that JupyterNotebooks are really nice to explain a recipe but LP is better at explaining ideas (more like algorithms).
However, usually algorithms presentations have more to do with ideas, concepts. And thus, one may wonder if even bothering writing an LP document is necessary since the core ideas don't actually need code to be explained).
This is solved by the Pluto notebook, which makes presentation order independent of execution order. Only works for Julia, but shows that such a notebook is possible.
Do you happen to have any example that shows LP in Pluto? The site you linked does not show it, at least looking at the screenshots, I don't see anything like I would have in org-mode using org-babel.
WRT modern tooling, I was thinking more in terms of languages like Haskell that don’t constrain the order of presentation so much, or tools like rustdoc that embrace hypertext and allow the reader to choose their own adventure.
It's one of two literate programming books I've read—the other one is PBRT, which provides about the same level of quality as the TeXBook IMO (I haven't read metafont).
> On balance I think modern tools achieve Knuth’s goals better than tangle/weave. Modern languages let you organize code much closer to its narrative structure, without WEB’s unhygienic scoping. Editors and IDEs and documentation browsers give you hyperlinks everywhere.
I just don't think this is a technical concern to begin with. It's true that you can jump around code a lot easier, but this doesn't make the issue of laying out the code as a linear narrative with interleaved code and text any easier of a task. I do think it's an excellent, excellent way to present code, though.
I think "linear narrative" is exactly the hard part. Most programs don't have a linear narrative, they have a "hub-and-spoke" narrative of some sort, a central core then a bunch of branches of "features" that may or may not interact. Some of that is "do one thing" is great in the Unix philosophy but doesn't really describe things like business-oriented development. But I think more interestingly as Diataxis [0] maybe suggests learning about any project is maybe necessarily "two-dimensional" at best/least. Someone entirely new to a codebase is probably going to need a different "narrative" than someone familiar with it. Maintenance and new work are different narratives from the original design.
Perhaps literate programming is so "hard" simply because we aren't able to nail down a single linear narrative.
I don't know what the Diataxis of Literate Programming looks like, but it does wriggle with some ideas.
In my own current project I've found that it's important to remember that there are at least two different audiences for the documentation:
- the user who wants the bare minimum, perhaps a template and a brief discussion of each command, ideally wrapped up to be as friendly as possible
- myself and other programmers who want the innermost workings explained in detail
The typical user of TeX doesn't want _The TeXbook_, nor even a Literate version of Plain TeX (and why that doesn't exist is a different discussion), but rather lshort.pdf (and they _don't_ want the typeset .dtx source of latex2e --- I actually printed that out once, and have yet to read it, since I still need to compile a list of texts I need to read/concepts I need to understand in order to do so profitably).
Yes the problem is all these different literate needs.
There is a whole range of things we should want to document and encompass. From an outline idea or plan of how the software is planned to work and is currently implemented, all the way to more like programming journal or engineering notebook on the development, showing stuff that was tried and failed, record of performance experiments, all the way to day to day commits. Day to day commits is probably enough quantity that it will do fine with a separate system - but then should still have pointers or references in the engineering notebook aspect of the whole thing. And then of course multi-user by now. And for many software bases, this cannot be linked to "in order" execution like a python notebook. Execution is too variable, long, on-going and environment dependent. It's possible that what this ends up looking like is pairing "extensive in-code documentation" with a separate "overview narrative" and a separate "engineering journal" (with thoughts and rationales and test results pasted in or git-ted.)
But I don't throw stones at "typesetting". Nowadays "mind-map" is probably more appropriate or free-form layout, and there is a lot to be said for throwing low cost graphical representations including napkin diagrams here and there in the documentation. If we are trying to make it easy on the programmers to provide all this input, then let's make it easy.
(But then I object to the lack of "linearity, diff-ability, text-ability" of mind-map formats and my reaction to spreadsheets like one of the comments requests: "oh god no, let's not HIDE all this in countless tiny little boxes that must be opened one by one!" - but I would love a linear, text-based computable spreadsheet format.)
> The order of presentation of a program is dictated by the needs of a single-pass compiler
That never really made sense to me. All those "if"s, and "while"s, and "break"s, and "return"s jump to the not-yet-generated places in code just fine; a similar technique could be used for delaying the resolution of function/procedure calls as well.
Now, generating initialized data/rodata sections is something that single-pass compilers do struggle with (that's why Pascal didn't have array literals), and it's understandable: the Modula compiler, which got them (or was it Modula-2?), had to hold all of that initialized data section in memory and then dump it to the disk only after the code-generation (and patching the offsets) was done. But dealing with not-yet-defined code labels? That's something you have to do anyhow.
> All those "if"s, and "while"s, and "break"s, and "return"s jump to the not-yet-generated places in code just fine; a similar technique could be used for delaying the resolution of function/procedure calls as well.
Not quite. When compiling "if"s, and "while"s, and "break"s, a compiler will make use of the fact that code is structured. Because of that, a stack of addresses is sufficient to track (similar to how Forth uses the return stack to compile such constructs)
For returns, the compiler doesn’t resolve the target address; that happens at runtime.
For function calls, a compiler would need a map mapping function names to addresses.
Also, generating good error messages is harder if you allow forward references. You cannot generate an error before seeing the definition, even if, say you encounter two calls to foo before seeing its definition.
Of course you can do all of this! After all, the sources of e.g. Wirth's Modula-2 single-pass compiler for the Macintosh (that generates native MC68000 code) have survived, and it doesn't use "stack of addresses": it uses "fixup()" calls when compiling conditions, loops, references to (imported) global variables etc. and, surely enough, calls to functions. It even has "CheckUDProc()" call at the end of the module-translation procedure to check whether there are any undefined procedures left in the symbol table.
It would be entirely possible to treat calls to undefined functions as implicit forward declarations; he simply never chose to do so.
The problem with functions is that when you see something like this:
F(X);
there may be implicit conversions happening to X here depending on its type, and, crucially, the declared type of the argument - e.g. Integer to Real. This would be a separate function call in many cases, so it's not just a single address to patch - indeed, you don't even know the size of the code that will need to be inserted in advance.
Yes, and the way that e.g. C famously handles it is that it treats it as X being implicitly int, if it's an integral expression that produces something shorter than long, and a double, if X is a floating-point-valued expression, and whatever happens to structs when invoking implicitly-declared functions.
This, again, can be checked for the correctness at the end of the translation of a particular compilation unit, C just decided to not bother with this at all and leave it to the linker to figure out (even though it started its life as a language with 2-and-a-half passes compiler), and Pascal and its descendants decided to require either forward declarations, or using function pointers. I personally think that e.g.
var F: proc(whatever): whatever;
proc G() begin
... F(whatever); ...
end G;
proc F0(whatever): whatever begin
... G(); ...
end F0;
begin
F := F0;
end module.
is a needless pessimization but e.g. Oberon does it this way.
> You just feed your TeX document to ghc and it picks the code out of the \begin{code} blocks.
This is starting to remind me of Jupyter notebooks, as you are expected to have documentation (well-rendered markdown) blocks and dedicated code blocks. Now I wonder if notebooks were conceived with Knuth's vision in mind.
The cosine adjustment that Google originally used is not the best: NTP aims to measure the difference in rate between the client’s hardware clock and real time, and it works best if the rates are fairly constant. With the cosine smear, the rate changes continuously! If you use a simple linear smear, NTP just has to cope with two small rate changes at the start and end of the smear.
The smear needs to be slow enough that NTP’s algorithms have time to react without overshooting; 24 hours is a reasonable choice tho you can go a bit faster. There’s some disagreement about when the smear occurs relative to the leap second; if the leap is in the middle of the smear the max offset is 0.5 seconds, but if the leap is at the end of the smear the offset is always slow. They were able to test the up-to-one-second-slow scenario in a system-wide live trial, whereas they could not do the same for the sign flip. I think if you can cope with a 0.5 second offset from real time then a 1.0 second offset should not be much more troublesome.
reply