Goto and the folly of dogma (2018)
manybutfinite.com
manybutfinite.com
Possibly the biggest lesson I've learned - both from the kiln of real-world project requirements (within a multi-paradigm language) and from my intentional ventures into other different and diverse programming languages and frameworks - is that there's no such thing. There's no perfect way of describing, not within a domain and certainly not across them. It's not just a matter of abstracting farther and farther from real-world concerns, sacrificing optimization until you're in descriptive nirvana. There are many good ways to describe a given thing in code, and there are many more bad ways, but there's no perfect way. Once I grasped that I became a much better (and less stressed) programmer.
Infact the best abstraction may depend on the team who will be maintaining that code - so whether to use Tech A or B or Pattern X or Y might have, as an important factor, whether you are moving office from one city to another, and whether the job market is good or bad, affecting flow of people in or out of the company etc.
Taboos tend to accrete over time. For example, overzealous object-oriented design has produced a lot of lasagna code (too many layers) and a tendency towards overly complex designs. Chasing semantic markup purity, we sometimes resorted to hideous and even unreliable CSS hacks when much simpler solutions were available in HTML. Now, with microservices, people sometimes break up a trivial app into a hard-to-follow spiderweb of components. Again, these are cases of people taking a valuable guideline for an end in itself. Always keep a hard-nosed pragmatic aim at the real goals: simplicity, clarity, generality.
From Java's "AbstractFactoryBuilderDelegator" insanity to "nanoservices", the common thread to me seems to be overzealous decoupling, to the point where I need to look in 10 different locations just to find out what happens during a single request.
At least Dijkstra had sound mathematical reasoning for his arguments and wrote about them eloquently (and with good humor I may add); most of what is peddled in the hipster coding circles is a smooth talk by a gifted social media frontman that has no solid basis in anything besides that the person is popular. I do not even understand how people dare to put their name on complete messes like npm or one line npm packages unless it is a joke. I assume things like leftpad are in fact a joke; if they are not I would have to cry myself to sleep every night. So I just lie and say it is funny.
Only when someone codes something without any of that and it gets popular or makes a lot of money, people come with ‘it was best for this occassion’. The best example I can think off being anything Arther Whitney (k/kbd+) does; his softare makes a ton of money, it is faster, smaller and, in my opinion, easier to debug and uses less resources than most things I have ever seen passing here (including what people call embedded; no people, something with a gig of memory is not emdedded) and yet it pukes over almost all rules and styleguides that everyone loves so much. Not to mention: he does something a lot of programmers are jealous off (including me); he makes money with a programming language and is always used here as a counter example when people shout that programming languages that are not opensource and/or are commercial (even very costly) do not work.
I wanted to write one sentence; it became slightly more, but I guess most of it is on topic.
Edit: I did not mean the last part sarcastic although it reads like that; I think Zuckerberg is a vastly overrated twat but that or that the product of the company currently sucks (yes yes IMHO, but many people agree and I mean currently; it could be great, but shareholder value) does not have anything todo with technical merit.
They're not the ones learning it but they're still attending all of the conferences for it and with a non-practiced engineering capability they're back to cargo cult BS.
Best to keep learning the new hotness or it's career suicide. Just remember, for almost any 9-5 it's about the _narrative_ of work more than it is about the work. Rewriting/changing huge portions of your already-working tech stack is job security. I truly believe a huge portion of engineers engage in their own "make-work" to justify their existence/paycheck.
If it were decoupling in any meaningful sense, you wouldn't need to look in 10 different locations. But you need to look, because it's all related and tightly coupled!
https://github.com/python/cpython/search?q=goto
There's a lot of "goto exit" which is obviously a CPython runtime convention - fair enough. However there's plenty of classically bad code, example:
https://gist.github.com/ridiculousfish/ffe4fa2a17c831ed06e57...
These are old-school-bad gotos: `if` statements would do the job more clearly. Is this a broken-window phenomenon: one planted `goto` opens the door for the rest? Or is there a deeper motivation for this style?
> We should be willing to break generic rules when the circumstances call for it. Keep it simple.
I argue we should instead iterate on the programming language design to make sure we don't need to make these kinds of trade-offs.
At its heart it is just a bytecode interpreter, i.e. a loop with a huge switch. I don't think you need an elephant language just for that.
Graceful error exiting is to this day an unsolved problem in computer science (at least as far as the popular languages are concerned). Even after GOTO elimination hit its stride, Knuth noted:
"Another important class of go to statements is an error exit. Such checks on the validity of data are very important, especially in software, and it seems to be the one class of go to's that still is considered ugly but necessary by today's leading reformers. Sometimes it is necessary to exit from several levels of control, cutting across code that may even have been written by other programmers; and the most graceful way to do this is a direct approach with a go to or its equivalent. Then the intermediate levels of the program can be written under the assumption that nothing will go wrong."
And yet, there is no goto/label in the Python language itself.
from libc.setjmp cimport jmp_buf, longjmp, setjmpAs for why you'd want to write a goto where an if would be semantically equivalent, there's a mix of style and human-level semantics: gotos are for "exceptional" cases, so the "normal" case looks flat and falls though directly to unconditional code, keeping the main logic flow at a consistent indentation level. (And apparently this heuristic is also baked into simple branch predictors, though I doubt that's something that comes up nearly as much.)
A=add(B,C)
You had to write
Adder AA; A=AA.Add(B,C)
I remember endless discussions about this and people always argued that functions are not OO whereas I said OO is about state so no OO needed for adding two numbers.
Same with goto. In FORTRAN it was an essential tool but suddenly it became illegal and you had to write complex if statements and other things just to get the same effect.
I guess software is so complex that it’s very to always understand all drawbacks and advantages of something so you have to live by a set of rules that usually work and follow them blindly.
Here are some other things that Adder AA; A=AA.Add(B,C) has over A=add(B,C) that you are glossing over
1) You move the adding logic to it's own file, with other adding-only responsibilities
2) You allow injecting different implementations of Adder - maybe introducing a more efficient one in some cases but not others
3) You enable mocking Adder so that your logic that verifies that B and C are added can be tested without having to re-add B and C all the time.
Not sure how much of a concern this was at the time when OO paradigms were first being created, but in today's world where .Add might be a call to different cloud services, and where unit tests with good mocking are essential for any serious application, these concepts are essential.
The code snippet above says nothing about where the adding logic is, other than that one places it in a method while another uses a straight function. Said method/function can live anywhere, unless you have a specific language in mind that prevents you from moving functions to a file?
> 2) You allow injecting different implementations of Adder - maybe introducing a more efficient one in some cases but not others
The thing the code wants to do is add something, so if that can be made more efficient, it can be made more efficient by implementing a more efficient add function. Why is it that you need to introduce something that is not an add function, in order to inject a more efficient add function? Why can you inject an Adder but not add()? What language is that again?
> 3) You enable mocking Adder so that your logic that verifies that B and C are added can be tested without having to re-add B and C all the time.
This makes no sense to me. If you add B and C, you test it once, so what's the deal with this re-adding? And why can't you mock add()?
I (still) work with a Classic ASP code base.
I kid you not, that there are VBScript functions, which call JScript (not JavaScript) functions, which in turn call VBScript functions.
It is a terrifying and glorious mess.
edit:
I also worked with a system at one time that used node.js to create C# files on the file system, then use the C# compiler to create an executable and then run it. It was... not great.
Did anyone actually ever do that or is this just a huge red herring?
Also, the above looks more like a data flow language where adders are necessarily components in the wiring diagram (try building a CPU without adders!).
Add can be a virtual method on B (so B.add(C)), but then you really want Dylan-esque multidispatch on both B and C. But those kind of debates fell out of style with the 90s.
Really deeply convoluted OO.
Yes people did that and still do.
It's almost like an anti-vax argument. "This disease doesn't exist anymore, why are we cargo-culting by vaccinating against it?"
The argument in the original rant was about the limits of our ability to reason about code, and remains a deep and useful insight. The fact that we don't really have examples of non-structured codebases to point to in 2019 shows how essential the invention of it was to our work.
Fortunately we have better models that can handle goto. So it's not really like the anti-vax movement because the most substantial thing that changed isn't our use of goto (the disease burden in your analogy) but better formalizations (i.e. we have better medicine that makes fewer demands of the patient).
Indeed the best way to understand what they can and can not do is precisely to understand that formalization model and understand when you can't do a certain thing because it breaks it.
Instead, I'll have to confess ignorance; I didn't realize that C's goto was that insane but it seems it is. https://stackoverflow.com/questions/6021942/c-c-goto-into-th...
I'll have to ask the reader to suitably modify the post above. The tamed goto made available in most modern high level languages can't break the analysis, because the compiler will reject it, i.e. https://play.golang.org/p/2tA7Bpof611 (hit "run" to (try to) compile)
"In the late 1960's we witnessed a "software crisis", which many people thought was paradoxical because programming was supposed to be so easy. As a result of the crisis, people are now beginning to renounce every feature of programming that can be considered guilty by virtue of its association with difficulties. Not only go to statements are being questioned; we also hear complaints about floating-point calculations, global variables, semaphores, pointer variables, and even assignment statements. Soon we might be restricted to only a dozen or so programs that are sufficiently simple to be allowable; then we will be almost certain that these programs cannot lead us into any trouble, but of course we won't be able to solve many problems."
It's a problem as old as time itself: A smart person makes an observation based on deep understanding, and the rest, rather than go through the cognitive load of learning its fundamental roots, convert it to an easy statement of morality and dogma, shrouding it deeper and deeper with ceremony and pomp to create a mystique that none dare investigate.
Thinking is hard, and takes much energy. Most people prefer to keep that to a minimum, thus our superstitions, dogmas, cults, and priesthoods.
I agree.
But I think it's worth pointing out that if we're going to use reluctance to use goto as an example of dogma, it strengthens the anti-dogma argument even more to point out that the dogma isn't even correct on its own terms; the goto that the dogma is rejecting historically isn't the same goto that exists today.
Under many dogmas lies a kernel of truth. That kernel can be worth extracting, and is often quite enlightening, unlike the dogma.
Very much this.
The programming world at that time was very much different than it is today. Fortran, which was thought of as a higher level language, had this abomination for IF statements that (a) required an arithmetic comparison and (b) had three possible destinations: one for the less than value, one for the equals value, one for the greater. It was extremely easy to get yourself into a full conceptual overload for any interesting program. Targets of branches were statement numbers. No language in wide use today has this problem.
>the original rant
If you go back and read it, it isn't so much a rant as a very logical reasoned description of the difficulties we were all feeling at the time as programmers. It was the rest of us (me included) that were part of the screaming crowd saying "Down with the GOTO totally!" Some of this unfortunate resulting hype was caused by the title of the article, not chosen by Dijkstra.
Knuth's response, as usual, has good humor in it. He notes a Dr. Eiichi Goto of Japan complained that he was always being eliminated. The concept of GOTO-less languages was also put forth in the XPL compiler written by McKeeman, Horining and Wortman, which incidentally was my introduction to compilers. Knuth also mentions Bliss, a very fascinating language, whose designers ultimately recognized that they had gone too far.
The author of TFA touches on another over zealousness in today's design thinking, and that is of object oriented programming. In another article or talk, Dijkstra is quoted as classifying object-oriented programming, along with other endeavors as part of a flourishing snakeoil business. A position which obviously enraged Alan Kay.
That so much C code is littered with them just demonstrates a deep weakness in C, and not any kind of fundamental principle. I admit surprise that C# turns out similarly weak.
It's one thing to prefer to work with another abstraction, but this is awfully judgmental phrasing that denies or unfairly maligns a usefulness and necessary ubiquity at a different level.
And yet, everything you run is written in it. (By a compiler or a JIT, sure.) The goto is a useful abstraction for its layer. It doesn't have to be your favorite layer, but it's there, and ubiquitous.
I feel like discussions around memory safety are similar. I can't get a lot of people around here to admit that in order to be blessed with memory safety at one layer it needs to not exist somewhere else, and that's OK.
Yes, jmp is useful at the assembly layer. Yes, everything eventually gets run on assembly (on the way to microcode, and then transistors, and then quantum mechanics). That doesn't mean most of us want to work there, though.
And goto is the same. Having seen that we don't have to work in that way, we don't want to work in that way. We can work with larger abstractions so that we don't have to deal with that kind of detail.
Correct. This is what I said all along. Glad to see you're up to speed.
Meanwhile, every time you write an if statement ... May you think, acknowledge, appreciate: "I'm adding a goto!" Or possibly several of them. [I am pretty sure I have had discussions with people who say they are also against if statements, but I don't think that's quite as common.]
What you are missing, and is the fundamental essence of the whole discussion, is that these jmp instructions don't just jump to any old place, like a goto. They jump to only very specific places corresponding to the boundaries of our if and while statements. The compiler will never generate an undisciplined branch, absent an actual goto in the source.
Beneath the jmp instructions there are register transfer machines, and beneath them are logic gates, and beneath them are transistors and wires, and beneath them are charge carriers and fields, and beneath those are atoms and crystalline structure.
At each level you can find the correspondence with structures in the next level above and below. In no case does the lower level violate the structural rules of the next level up, despite that in principle, it could. That is how we get systems that can be understood, and work.
Please don't assume that any notion of my own cleverness is the crux of what I am saying or has anything to do with it.
> At each level you can find the correspondence with structures in the next level above and below. In no case does the lower level violate the structural rules of the next level up, despite that in principle, it could
Disagree, especially since you went so far as to talk about the physics. There is a lot of order created from chaos, and the structural rules are largely fiction, taking some effort to impose them.
But in any case, and to the point, there is nothing fundamental about jmp instructions. They, and the sequential execution they interrupt, are a way to help organize state machines. It is a triumph of decades of effort that we have succeeded in making state machines of such complexity behave in comprehensible ways, and a deep failure that we have not found any better way.
I sort of disagree that C is "weak" -- I think its simplicity, which even includes `goto`, is a strength. But I agree that other languages can certainly, within their own contexts, come up with nice things.
So I argue the issue is not with goto per se, it is with the lack of better tools provided by the languages in question to express complicated control flow. Like many things in programming languages, better tools are well studied but not available in most mainstream languages, which are stuck in ~1980s paradigm.
Except that desktop is the only place standing where microkernel haven't fully catched up, and even then macOS and Windows have a kind of compromise between monolithic and microkernels, with plenty of stuff running on userspace, increasing with each release.
Even Project Treble pushes several drivers into userspace processes, with Android IPC to talk with the kernel layer.
Had Hurd gotten the same love from IBM, Compaq, Oracle, Intel,.... as Linux did, and it might have turned out quite differently.
You didn't "parse HTML" with a regex; you created a solution to fix a very narrowly circumscribed problem by pattern matching on some string inputs. Big difference. Were an easy to use HTML parser (or likely lexer) readily available there'd be little excuse to cut corners as the proper solution would likely be far easier to prove correct (formally or informally) than the regex hack. (Full disclosure: I've written an HTML5-compliant streaming HTML lexer precisely so I--and others--would have less reason to depend on regex hacks in security scanners.)
The article says that the Linux approach proved good enough. No, it didn't. Linux has turned into a nightmare of security vulnerabilities, on par with Windows 95, just as originally prophesied. We only tell ourselves it's good enough because we're unwilling to admit we're where at. Remember when Linux and open source were paragons of security? Man, how times have changed....
But now we have a formally verified operating system in seL4, which is... [wait for it...] a microkernel. Of course, it's difficult to use as a general purpose OS, though not far from where Linux was in the 1990s. In time we'll get there. In the meantime no good comes from lying to ourselves about the nature of our solutions.
I remember a time when Linux was a paragon of security compared to the corresponding Windows version, Windows 95. I do not remember a time when Linux had no vulnerabilities. What happened is not that Linux got worse but that Windows got much better.
What exactly are you talking about ? What was 'originally prophesied' ?