5 ms·
Recently, I've been reading the TeX program by Donald Knuth, and some related programs. One the one hand, they are intended to be read (written using the “liter
by svat 8y ago
Recently, I've been reading the TeX program by Donald Knuth, and some related programs. One the one hand, they are intended to be read (written using the “literate programming” style he developed), so they ought to be easier to read than typical programs (and maybe they are, compared to circa 1980 Pascal programs of similar size). On the other hand, I… struggle.
In these programs, Knuth uses very few abstractions. I think there are two reasons for this:
* These programs were written in the “standard” Pascal as of 1980, a language which had many limitations (see Kernighan's essay on the topic) — and on top of that, Knuth wrote these programs to be as portable and memory-efficient as possible, across the many (often poor) Pascal compilers that were available to typical users at typical computer installations.
* To some extent, I suspect it is just Knuth's style and preference. As a programmer who started his career writing assembly programs in his own style (a story from 1960: http://ed-thelen.org/comp-hist/B5000-AlgolRWaychoff.html#7 http://ed-thelen.org/comp-hist/B5000-AlgolRWaychoff.html#7), even when given a supposedly “high-level” language, he probably thinks very close to the machine. Even as a mathematician (a profession often believed to be fond of abstraction), his style is characterized less by abstraction-building and more by deep attention to detail and “just do it”: he calculates constants to 40 decimal places; he does not stop at analyzing Big-O complexity (despite having popularized the notation) but finds the actual constant factor; he does not analyze on an abstract model of computation but writes assembly programs for a specially-designed mythical computer and analyzes them; and so on.
Nevertheless, his literate programming seems to be a way of solving the problems for which abstraction is often used as a solution (comprehensibility, etc), without introducing abstraction (and paying its cost): you can have functions that are hundreds of lines long, but presented in understandable (dozen-line or so) chunks.
Here's an example from something I was reading today. Unfortunately to make sense of it you need to be somewhat familiar with the conventions of Pascal, of WEB, and the current place in the program, and turn off your instinctive horror of non-monospaced code and the weird (for today) indentation. (Some background here: https://shreevatsa.github.io/tex/program/pooltype/ https://shreevatsa.github.io/tex/program/pooltype/ and https://shreevatsa.github.io/tex/program/tangle/ https://shreevatsa.github.io/tex/program/tangle/) Anyway, the example is this: see sections 53 onwards: https://shreevatsa.github.io/tex/program/tangle/tangle-6 https://shreevatsa.github.io/tex/program/tangle/tangle-6 — here you have a single function `id_lookup` that is hundreds of lines long and has very complex case analysis and code paths, but chopped up in a way that makes it less forbidding to understand.
I think though that programmers today have a strong cultural bias in favour of abstraction, so they tend to have a strong negative reaction to code like this.
- microtherion 8y agoLiterate programming is ALL ABOUT abstraction! Every named section represents an abstraction. E.g. look at section 53 in the code you link to: begin l←id_loc−id_first; { compute the length } <Compute the hash code h 54>; <Compute the name location p 55>; if (p = name_ptr)∨(t = normal) then <Update the tables and check for possible errors 57>; id_lookup←p; end; What are the references to 54, 55, and 57, if not abstractions? I agree that Knuth style literate programming uses a quite different abstraction style than programmers are used to, and it's difficult to adopt and do well. But it IS using abstractions, and very extensively so.
- svat 8y agoThanks for reading. I understand what you mean, and I agree with you that what you identified is in some sense what literate programming is all about. I think debating whether that's precisely what is meant and is discussed as “abstraction” probably gets into motte-and-bailey (https://slatestarcodex.com/2014/11/03/all-in-all-another-brick-in-the-motte/?comments=false https://slatestarcodex.com/2014/11/03/all-in-all-another-bri...) territory: by defining “abstraction” sufficiently narrowly or sufficiently broadly, nearly anything being discussed in this thread can be said to be abstraction or not. For example, while here you're pointing out that giving a name to a bunch of consecutive lines of code is indeed abstraction (and I'm inclined to agree with you), currently the top comment on this thread argues that even splitting code into functions isn't “real” abstraction (https://news.ycombinator.com/item?id=19011659 https://news.ycombinator.com/item?id=19011659). So instead of trying to define “abstraction”, let's try to discuss the tradeoffs: I think what we're discussing is abstractions that are “formal” and “strong” versus those that are “lightweight” and “flimsy”. Look at the original example in the post: void main() { // Do A. ... // Do B. ... // Do C. ... // Do D. ... // Do E. ... } One could argue that here too there is “abstraction”, via the comments: they identify and document some related lines of code as doing something. When we move to the rewritten example: void bar() { // Do C. ... } void foo() { // Do B. ... bar(); // Do D. ... } void main() { // Do A. ... foo(); // Do E. ... } what's changed is that these abstractions have become more formal, more solid: grouping code into a function (I just left a comment about this: https://news.ycombinator.com/item?id=19012979 https://news.ycombinator.com/item?id=19012979) (instead of just with a comment) is a form of abstraction provided by the language, with stronger guarantees, such as that foo() cannot use variables that occur internal to bar(). Literate programming is somewhere between the two. The sectioning it provides is not a language-level construct; it's just textual (macro) substitution, and if you wrote something like that in C++ with #defines, you'd probably get yelled at for doing something dangerous instead of the abstractions (like functions) that the language provides. The literate-programming sections are very “flimsy”: code in one section can and does use variables defined in another (note all the function's variables are defined in section 53, and the function also uses global variables like `buffer` and `hash`); there's a “goto found” in section 56 where the label “found” is defined in section 55. Abstraction, beyond just naming/chunking, involves bringing into existence some new entity. The more “formal” this new entity is, the more you have to worry about things like: its place in, and relationship with, the rest of the system (e.g. what are all the function's callers); its interface/boundaries (under what circumstances does it make sense to call this function); how much of a clear meaning it has, and so on. With just a named section, there's less of that. The same things that make the latter kind of abstraction “flimsy” also make it have less cost.