What Fortran does better than C-like languages
craftofcoding.wordpress.com
craftofcoding.wordpress.com
The 'break' keyword does the exact same function as a goto, why not use a goto? The 'continue' keyword does the exact same function as a goto, why not use a goto?
... Actually, let's play the game even further. The 'else' keyword does the exact same function as a goto. So do 'while' and 'for' and 'do'/'while'. The only things you actually need are 'if' and 'goto', everything else is merely syntactic sugar on top of that.
But it turns out that syntactic sugar does have value. The break and continue keywords are nothing more than gotos that are restricted to breaking out of the innermost loop or continuing to the next loop iteration, respectively, and so you can understand what they do without having to track down the label (or indeed, come up with a name for the label!). It's more mystifying to me that some languages don't extend the syntax to refer to an arbitrary loop in the current loop nest.
In fact, it turns out that labeled break and continue are sufficient to allow you form nearly every possible control-flow graph... and the kind that it doesn't is the one I don't want you to be able to form, speaking as a writer of compiler optimizations.
A break from a named loop is one statement and a label. A goto is one statement and a label. You don’t lose structure by putting the label at the end of the loop instead of the beginning.
Unnamed break is incredibly common, so having a built-in shorthand is quite ergonomic; it also lets you avoid defining a label, which named break doesn’t do. The argument for continue is similar, but continue is less common and maybe doesn’t justify its keyword status. else, for, and do-while all take blocks, so they have the opportunity to encode structure in a way that a break from a named loop doesn’t.
Structured programming is not just a syntax sugar: the restriction encourages more readable programs https://blog.acolyer.org/2016/10/13/go-to-statement-consider...
In dijkstra's world, people just jumped to any code anywhere in the program, leading to insane spaghetti code that was impossible to read. In C++, goto can no longer jump to anywhere in the program, only within the function. This severely limits the potential damage of goto if the functions are otherwise well constructed.
goto can be used for high performance decision trees, loop exiting, and in C (less so in C++ due to RAII) for freeing memory at the end of a function. I used to fear goto, no longer. However, I would advocate using it only in those situations (possibly others) as in excess it will lead back to the same problems identified by Dijkstra.
1. Breaking out of nested loops. Sure, you can kludge this with a flag variable, but goto is far more readable.
2. Implementing resource cleanup with stack-style unwinding semantics (i.e. RAII-like). The alternative is usually a bunch of nested if statements, which makes things much harder to read.
3. Implementing state machines. goto is very good at expressing transitions in a graph-like state machine. It doesn't inflate code size as using inline functions would, and it doesn't incur register/stack setup costs that non-inline functions would. However, readability becomes somewhat difficult as the state machine gets more complex, so this is usually relegated to code generators like SMC, re2c, or Ragel.
4. Dispatch loops for threaded virtual bytecode. This is especially niche, and it relies on the labels-as-values gcc extension, but it is very important for implementing performant interpreters.
The average C programmer won't necessarily encounter all of these use cases in their career, but that doesn't make any individual one any less important.
If anything, the value of C's goto is evidence in favor of Dijkstra's argument more than against it. Real-world programs don't need real gotos, and the situations where you want them are perfectly well-served by constrained, limited replacements. Like function calls, for-loops, and the thingy that C calls a "goto."
For the sake of completeness: there's two main ways in which C's gotos are limited. The first is that they are function-scoped, the second is that they are declared labels rather than arbitrary program points. We nowadays consider that encapsulating data is a good thing. The idea of encapsulated control flow is so taken-for-granted that we don't even realize it could otherwise, but that's effectively what a true goto is.
Perhaps a nitpick: you presumably mean tail-call optimisation, which isn't the same thing as a tail-call.
To my knowledge modern C compilers are generally able to perform the optimisation.
I believe Java has a hard time with tail-call optimisation as its exception-handling features require it to faithfully track the call stack in case an exception is thrown.
Tail calls consuming unnecessary stack space is a common pessimisation.
Also, I'd argue that you should generally try to avoid doing doubly nested loops with lots of conditionals that you check to determine whether to break or continue. That seems like a code smell.
The `return handleErr(val);` is a nicer way to do it in some cases. I'll keep that in mind though you may have to pass a lot of references to the function for it to work.
I read an old edition of Numerical Recipes, and the first chapter or two was a paean to structured programming, which is when I realised that for, while etc hadn't sprung full-form from the heads of the first compiler developers.
I think the underlying reason is that it is annoying to write tree walking interpreters and implement goto:s. (How do you even do that?). So the prominent CS folks at the time just dismissed goto:s instead by bullying user to submission.
But the contempt goes further than "goto vs subroutine calls" or "goto vs the if-else construct".
With "underlying reason" I meant more like "underlying reason for the contempt of all use of goto". Even when goto makes the code less spaghetti and more readable.
Knuth did a good write up on the matter: https://pic.plover.com/knuth-GOTO.pdf
> I think the underlying reason is that it is annoying to write tree walking interpreters and implement goto:s
I don't believe that there is any historical justification for this view. And your updated statement is even less historically sound. Everything written about go to statements (as a negative, something to partially or fully eliminate) seems to have been predicated on reasonability and comprehensibility of the code by people, not the ability to write tree walking interpreters. Which is a minor activity when reviewing the totality of all computer programming activities of the past 70 or so years.
I only use it in Lua, to compensate for a lack of `continue` in loops.
cybrpnk:~/src/linux$ grep -rniE 'goto\W+.*;' . | grep -E '\.[c|h]:' | wc -l
182044
Sure.. I mean.. 'almost'1. One need for goto in C is for what is done with labelled loops in other languages. So one might expect uses there
2. Another use is for cleanup after errors which would be automatically handled by the compiler (via destructors) in most more modern non-gc languages. It looks like:
int do_thing(args…){
foo *x; bar *y; baz *z; int err;
if((err = make_foo(&x, …)) < 0) goto exit3;
if((err = make_bar(&y, …)) < 0) goto exit2;
if((err = make_baz(&z, …)) < 0) goto exit1;
frob(foo, bar, baz);
err = 0; /* maybe use x,y,z and return instead of cleanup */
free_baz(z);
exit1: free_bar(y);
exit2: free_foo(x);
exit3: return err;
}
So really all you’re proving is that C does not provide sufficiently useful structured programming tools to avoid goto. Java handles this fine for memory but not so nicely for objects that have more involved cleanup that shouldn’t be delayed (e.g. open files, removing from some parent object, etc). Indeed Java also doesn’t provide great tools for protecting cleanup code from stack unwindings, or at least they aren’t sufficiently ergonomic that you see them used everywhere.It certainly isn’t the case that Linux is full of old-school do-whatever-you-like uses of goto.
If you use f77(1) in the form of f2c, which I don't have to hand, I changing the value of 1 won't work these days (at least with GCC). It will give a SEGV, because you can expect the C compiler to put constants in read-only storage.
MMUs with protected memory, CDS, and no-execute flags are all relatively new concepts that appeared after FORTRAN was created. It's nice that f2c/gcc fixes some of these problems.
Regarding different sorts of argument passing, ALGOL 60 had call-by-value, -by-reference, and -by-name.
pure subroutine mean_and_std(vec, mean, std)
real, intent(in) :: vec(:)
real, intent(out) :: mean, std
mean = sum(vec) / size(vec)
std = sqrt(sum((vec - mean)**2) / size(vec))
end subroutineIt was added in Fortran 90.
FTN7X
This said, it is a modern, very high performance computing language. It is easy to, as the article notes, adapt the language to fit the algorithm, rather than doing the reverse which C/C++/etc. demand. The only other language that I am aware of like that is Julia.
Julia has a REPL built in, and you need to use PackageCompiler.jl to compile AOT code. Then again fortran has a nascent REPL[1].
(Note: not OpenMPI).
See https://www.openmp.org/blog/openmp-api-speeds-up-autonomous-... for a description of an example application of this.
Both GNU and Clang support OpenMP pragmas.
Those "strong" tags are, obviously, a mistake in the webpage html code, and not part of Fortran syntax.
What's wrong with goto to do exactly what it's supposed to do?
You can of course wrap loop content into functions, and maybe the compiler will "out-optimize" that, but you will never get the performance of loops in a single function, nor you can conditionally jump from middle of one loop into another.
Trying to outperform Fortran relies on C developers putting the right annotations on the right places and never get them misplaced, otherwise it will trigger UB.
(Also, Julia gives you easy automatic differentiation and gpuification)
I'm curious why you would want multiple dispatch. Seems to me like either longer names or single dispatch would be enough. In "cgbtrf", the first three letters just tell you what kind of matrix are the input... "cgb" being a complex-valued band matrix. That leaves you only three letters to say what the function does.
I was happily using CLOS-like systems long before Julia, though. Does Julia actually beat SBCL, for instance, on such things?
Fortran has ad-hoc polymorphism, and 63 characters maximum length for names. These should be enough to keep the user happy in this use case.
(The programmer still needs to write separate code for each version of input types.)
So the language has built-in optimization for linear algebra kind of math since decades, doesn't rely on pointers for basic data structures and type system prevents aliasing by default.
What does the author mean by this? How can you get faster than any other compiled language like C where you can check the generated assembly? Does the Fortran compiler do anything special that others don't?
Not sure if there still exist semantic differences affecting optimizability to this day - perhaps things related to automatic vectorization?
You can break to labels. Even in Java. It’s like goto
A better design would be to just return from a function
But of course you are right, that one could just use goto for C/C++.