3 ms·
Dijkstra's paper is hard to read. I've gone through it and broken it down. It turns out, the central argument put forth in Dijkstra's paper is that the 'goto' s
by zzbzq 6y ago
Dijkstra's paper is hard to read. I've gone through it and broken it down. It turns out, the central argument put forth in Dijkstra's paper is that the 'goto' statement messes up the stack trace. Although I don't think the term "stack trace" existed yet, so he keeps talking about "textual indices."
The 'go' statement actually allows the cleanest stack traces of any language I've encountered. Go crash dumps can print 40 stack traces for 40 threads and still be shorter, cleaner, easier to read than the stack traces of something like a single line of C# async/await code.
The article seems to argue that it's hard to run more code after the completion of a goroutine. But, the entire point is you don't have to. Go does async i/o under the hood, so you just write the lines of code sequentially in the order you want them to happen, and they will happen at the right time.
As a side note about the 'goto' statement, modern languages, if they have the statement at all, explicitly lock 'goto' usage into one stack frame anyway. I'm 100% unconvinced there's anything wrong with it in those instances. Anything bad things you can do with it can be done wrong as easily with other control flow.
- goto11 6y agoDijkstra is not talking about stack traces. Structured programming is not just about functions, it is also control structure blocks like if or while-blocks instead of goto's, which does not affect stack traces. Dijkstra is talking about describing and analyzing the execution of a program. With structured programming, the execution of a program can be described as a tree of executed blocks. A program using goto's is a tangled graph, since every point can jump to any other point. This makes it much harder to describe and analyze. I doubt Dijkstra cared much about stack traces, since he didn't care for debugging at all. For him, programs was something you analyzed on paper and proved correct. As for gotos being constrained to a single function, this does not make any difference for Dijkstras point, since a single function can still contain the same complexity as a whole unstructured program.
- Banana699 6y ago(Imperative) Programs cannot be pure trees, looping constructs necessarily introduce cycles. What structured programming is all about is to make those cycles as well-behaved as possible. You are largely right nonetheless, Dijkstra's argument could be summarized as: 1- Understanding code depends on imagining it's dynamic effects from its static description 2- As we progressively make code more powerful (executing single statements in sequence -->+ executing either of two statements depending on the current state -->+ aggregation of statement blocks into subroutines -->+ looping constructs); the job of the entity reading your code becomes progressively harder as it tries to reason about: a List -->+ a Tree -->+ a generalized extra-recursive tree where nodes are themselves trees -->+ that last kind of tree but with cycles. 3-GoTos create especially-nasty uber-unpredictable networks of cycles while adding no more power than other well-behaved options. The essence of the argument is a tale as old as time: increasing the Freedom of Expression for the writer increases the Burden of Understanding on the reader. Stack traces, in a generalized kind of way, is another equivalent form of describing Dijkstra's argument. The previous way of explaining Dijkstra's argument imagines an idealized reader (could be an actual person, a static-analysis tool trying to predict some property of your code, or any other "reader") and asks you to consider the complexity of the data structure it tries to construct to mentally represent your program in static form. Now imagine it the other way: the reader is seeing your program's dynamical behavior in a "debugger" (could be an actual debugger, could be a sheet of a paper with a bunch of trace tables,...), how does the amount and complexity of the data structures that this idealized reader needs to follow the behaviour of your code scales as the complexity of your code scales? For purely sequential programs , that 's just a single counter keeping track of where we're in the program . Add conditionals, and you now have to keep track of a flag indicating for each line which branch was taken. Add blocks or subroutines , and you are now maintaining a whole stack of such counters/flags. So far, so linear, you are still limited to incrementing counters, raising flags, and adding or removing counters/flags from the top of the stack. But you're never decrementing counters (going back to a previous point in execution) or touching the stack in the middle. Gotos and loops destroy this: Gotos are the worst of the pair as it arbitrarily jumps you to any counter on the whole stack without even recording the point of return. The whole concept of an "execution stack" becomes meaningless, program execution is a graph that you maintain in your head (or in the debugger) as it threads through the code. Other looping constructs are more well-behaved in that they state beforehand that a jump will happen and also the fixed whereabouts of its destination. The argument is that since different constructs give you the equivalent power but complicate your code's dynamic behavior asymmetrically, you should choose some of them and ban others. The first version of the argument imagines code understanding as a writer-reader dynamic and talks about the reader reasoning about the static form of the programs written by the writer, the second imagines it as a dancer-viewer dynamic and talks about the viewer following the flow of the program orchestrated by the dance choreographer (the programmer). Two ways of saying the same thing.
- curryst 6y ago> The article seems to argue that it's hard to run more code after the completion of a goroutine. But, the entire point is you don't have to. Go does async i/o under the hood, so you just write the lines of code sequentially in the order you want them to happen, and they will happen at the right time. That seems a little hard to believe. If I do this: db, _ := sql.Dial("") defer db.Close() go myFunc(db) It will close my DB connection while myFunc is still running (assuming myFunc isn't trivial enough that it sometimes runs before the close can happen). The author seems like they were more concerned about how 'go' tends to break abstractions. The additional flow control means that I do actually have to care how the library is implemented. I have to understand how they handle the lifecycle of objects I pass in, and then I have to figure out how to stop their stuff (and to stop it at the right time). DB connections are one of the things in Go that are frequently not closed or not closed correctly. The lack of a "with" kind of statement requires me to write a bunch of IPC so I can tell goroutines to quit, then wait for them to quit, and then finally close the connection. A lot of people (myself included when I'm being a bad programmer) just call 'os.Exit' and figure the DB will clean up the stale connections.
- mykowebhn 6y agoUsing a waitgroup will fix your problem in your example.
- cratermoon 6y agoGo convention would be to pass a 'done' channel to myFunc and only close the db on receipt of a message on the channel.
- Jtsummers 6y agohttps://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/EWD215.html https://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/E... (for a reference for others) Your statement: > It turns out, the central argument put forth in Dijkstra's paper is that the 'goto' statement messes up the stack trace. Didn't sit right with my recollection so I reread it, it's brief so I recommend others take the time to read it themselves (I think it's one of those short and easy texts that the vast majority people have heard of and reference without ever having read, so be the change and read it if you haven't). His central thesis is laid out in paragraphs two and three, with the rest being an attempt at explaining why the go to statement creates problems and his suggested improvement: > My first remark is that, although the programmer's activity ends when he has constructed a correct program, the process taking place under control of his program is the true subject matter of his activity, for it is this process that has to accomplish the desired effect; it is this process that in its dynamic behavior has to satisfy the desired specifications. Yet, once the program has been made, the "making' of the corresponding process is delegated to the machine. > My second remark is that our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible. In summary, shortening the semantic distance between the static representation and the dynamic execution improves our ability to understand that dynamic execution. And that is critical because it's the actual thing we, as programmers, are producing.