58 comments

[ 3.5 ms ] story [ 121 ms ] thread
I mean the lesson I'd take from that is "don't use a buggy compiler". If you can't trust your tools then it's very hard to make something you can trust with them. That changing the code means it compiled in an incorrect way that your test case happened to hit doesn't mean the previous version of the code was being compiled correctly, or was correct code.
The lesson I take from this is to steer well clear of historic languages with the many intricacies and interdependencies described in the blog post. I assume that there is a reason why that old buggy compiler version is still in use - probably some legacy library or code that only works with that version of the compiler and that nobody has touched for 20-30 years and nobody wants to touch?
I’m defense of FPS 4.0, plenty of modern Fortran compilers have bugs too…
The best defense being a good offense.
I mean, I guess "don't produce or use buggy software" is good advice, depending on what function you're looking advice to do.
Fortran 66 was the pinnacle as a no-nonsense practical programming language. No semicolon cancer, no fancy brackets, just code that looks like code. Beautiful (in a nerdy kind of way, by today’s standards).
> Fortran 66 was the pinnacle as a no-nonsense practical programming language.

You got me by a major release. My introduction to Fortran was Fortran 77 on an antiquated IBM mainframe (event at that time).

Loved it, even if I could never remember what column I needed to use :-).

A Real Programmer can write FORTRAN IV code in any language.
The pinnacle... huh.

What about assigned goto? hollerith?equivalence? Please look at the following:

C THE CALLEE

SUBROUTINE X(I)

GO TO I

END

C THE CALLER ASSIGN 10 TO J

CALL X(J)

C THIS IS NEVER REACHED STOP 10 WRITE(3,100)

100 FORMAT(8H THIS IS)

END

Why would this work? Because recursive calls are not allowed, There should NOT be a stack. So allow rather arbitrary jumped around (setjmp/longjmp) is um... ok?!? And what happens if a normal integer is passed to that code? (and it is separately compiled). Yikes.

Or, with proper formatting:

  C    THE CALLEE
       SUBROUTINE X(I)
         GO TO I
       END
  C    THE CALLER 
       ASSIGN 10 TO J
       CALL X(J)
  C    THIS IS NEVER REACHED 
       STOP 
   10  WRITE(3,100)
   100 FORMAT(8H THIS IS)
       END
Nothing like characterization tests to help with work like this...
My favorite personal Fortran story: I was passing by a WAY SMARTER THAN I COULD EVER HOPE FOR guy who was pouring over a two inch Print-out of some scientific code. He had been trying to troubleshoot a problem. I guess a comma was in the 80th column, which I recall presents an error. In my passing by, I pointed out the error (one second) and I always had his respect after that.
Never underestimate the benefit of a fresh pair of eyes!
Hah. Years back a person from a forum from my college (this is just barely pre-Facebook) who absolutely hated my guts sheepishly asked for my help to look over some job related code of his that was giving him errors. He came to me because everyone said "I was good with web stuff".

Within about 5 seconds I spotted he had forgotten a semi-colon from the end of an escape character and it was causing him all hell with rendering. I pointed it out right away but still looked over the rest to find errors and didn't find anything. He had a hard time believing it but trusted me enough to try it and alas I was correct.

From then on he treated me much better.

A far better C programmer than I came by once sure he'd found a compiler bug. He had spent all day wondering why his loop only executed once:

    for (i = 0; i < 10; ++i);
        func();
In one second I pointed out the extra ;. This does not remotely mean he was a bad programmer and I was a great one. It's just exactly the kind of thing where we see what we expect to see, not what is there. I make misstakes like that, too.

But this experience is why D does not allow a solitary ; to represent an empty line. Modern C compilers spit out a warning for it, but geez, just make it an error already and eliminate the scourge of those bugs. I've seen other variations like:

    if (condition);
        doThis();
In high school (1996?) I was teaching myself C to write a small game and I had forgotten to add "break" at the end of each case within a switch statement. I was developing by myself (not quite in my mother's basement, but close enough) before I had access to the internet or stack overflow. It took me months to figure out the error. When I finally figured it out I was so embarrassed.
This seems to be a common pedagogical problem in expositions of C — so few of them point out that no, damn it, `switch` is not a multi-arm `if`, it’s a computed `goto` with lipstick on it (as opposed to an actual computed GO TO like the one in Fortran).
Many compilers allow for emitting the original source interleaved with the assembly output. It is a great way to understand the code, as well as stepping through in a debugger.

With small scale computation (single process, single node), we have control over the entire universe. We just have to realize it and then figure out how to introspect it.

@natnatenathan I am glad you stuck with it, but the cognitive flaw that is very hard to get over is broken partial information. We have to rollback (forget our knowledge) and then learn it again fresh which can be incredibly hard, we skip over things we "think" we already know.

> Many compilers allow for emitting the original source interleaved with the assembly output.

My object file disassembler will do that:

https://www.digitalmars.com/ctg/obj2asm.html

if the source code was compiled with debug info turned on. This feature has been around at least since the 80s.

So will objdump (or llvm-objdump) with -dS, for that matter (hint: -Mintel or, on older llvm-objdump versions, --x86-asm-syntax=intel), except the line numbers in both GCC and Clang’s debug info suck in optimized builds. (Not the fault of the debug info, arguably, it’s just that the optimizer rearranges the code to the extent that the notion of line numbers makes little sense.)
> the optimizer rearranges the code to the extent that the notion of line numbers makes little sense

I remember PhD papers addressing that in the 1980s. Nobody has yet figured out how to revert optimizer hamburger back into source meat.

Hah, when I took a c class back in the 90s, my teacher showed us a job listing from the classified ads -- it was a similar code snippet to your first, with "See the problem? Call us."

That memory should make me feel old, I think. Calling a company about a programming job listed in the paper, whose challenge is now a bog-standard compiler warning? Those were the days

This is why Ada disallows empty statements. You have to explicitly write "null;" which is a lot more obvious.
D's null statement is `{ }` which nobody is going to type by accident, whereas ';' is just a twitch of your pinky.

`null` has another meaning in D, that of a null pointer.

D also does not allow the syntax `a < b < c`. C does, to the eternal damnation of anyone who tries it.

> D also does not allow the syntax `a < b < c`. C does, to the eternal damnation of anyone who tries it.

Far be it for me to teach the Pope to pray, but would it be reasonably reasonable to make that do the right thing? If your compiler can disallow it, it can detect it, so the grammar is obviously thinkable, and the semantics might be as well.

I believe this is legal Python and does what you expect.
Most of the time it does what you expect. Sometimes it has its own pitfalls unfortunately. a in b == True is one weird example...
This is tricky, but it is at least a PEP 8 violation to compare a Boolean expression to True or False with ==, and a linter will complain. Programming in Python without a linter can be perilous in general.
I hate when people do that (in C++).
in lisp its (< a b c)

   The value of < is true if the numbers are in monotonically increasing order; otherwise it is false.
- clhs
The important thing is that it's unambiguous. One function is being called with three arguments. If you don't know what that function does, you look it up. No drama.
exactly. (< a b c) is much clearer than a < b < c
In the syntax `a < b < c` both the `<` are different operators than the `<` in `a < b`. It is more similar to the ternary operator `a ? b : c`.

It could also just be a type error: `<` takes two integers and produces a boolean, so both `(a < b) < c` and `a < (b < c)` would be invalid.

> would it be reasonably reasonable to make that do the right thing?

I did think about that, and it's a good question. I did not out of concern for some C code translated to D then mysteriously misbehaving.

This is why in Go you always have to have a block after a condition or loop statement - a single instruction is not accepted.
It's a really minor point, but this is one thing I like about Rust syntax for conditionals vs. C/C++. It always requires the {} around the block, so it has no special rule for single-statement conditionals like C. At the same time, always requiring the {} means that it can get rid of the () around the condition. So all in all, it's as short or shorter than the C style syntax, and clearer without special cases.
Looks like several newer languages adopted that idea - Go does it too (also getting rid of the round brackets for loops and conditions, as well as of semicolons at the end of lines), but I'm not sure who started it...
this sounds so silly to people writing in s-expressions
This is my problem as a lone amateur developer: I know they I made a mistake, I know they this is not a bug of the language, and I know the problem is obvious.

Yet I am left to myself and my cat to explain the problem. I usually managev to find it by explaining the problem to the cat, rubber duck style.

> I make misstakes like that, too.

I spent a good 30 seconds wondering if "++i" had some side-effect compared to the more usual "i++", before I saw the real problem, and I write C all day.

Yes. This is why linters are invaluable (or indeed languages that are stricter with syntax). Programming is so much easier now that we have this kind of tooling.

I remember trying to write my first programs in MS Notepad (which doesn't even have syntax highlighting) and then run them in Internet Explorer which would popup error messages one at a time in a dialog box. And a lot of time was spent finding and fixing trivial errors like this.

> Programming is so much easier now that we have this kind of tooling.

Are you an Ent? What you mean by "now" may not be what most other sentient beings understand here...

The program lint(1) was written in 1978, and the -W option for gcc appeared in the late eighties. For most of us, "this kind of tooling" has existed since before we were born. And I consider myself a graybeard!

Hah, I wish I was an Ent! But I wasn't even born until well after 1978.

However, specfically for JavaScript which is the language I was using when I first started programming it seems that the first linter wasn't released until 2011. At first there wasn't even `console.log`. I also think access to these tools has improved considerably. These days any introductory resource will point your towards VS Code which has a built-in marketplace which will install these tools for you at the click of a button (cross-platform!). That certainly wasn't the case even 10 years ago.

when I first found syntax highlight, it is a pure joy:)
One of the goals of D's design is to not need a linter.

(I once told the Coverity folks that the mission of D was to put Coverity out of business!)

Probably compilers allow that kind of for loop because you can construct a valid for loop that does actual work inside what should be the condition logic. And sure, that's horrifying and awful and people shouldn't do it, but the spec says that it's valid so the compiler has to allow it.
A friend of mine was getting a masters in computer science where they teach you all of the cs in the first class he is coming to business school and have a while loop with a semicolon at the end he had spent six and a half hours trying to figure out why it didn't work. I spent 2 minutes reading the code couldn't figure it out open up and debugger and then obviously saw it when the doctor wouldn't go past a while. It's kind of like when I spent 7 hours figuring out on my computer wouldn't start and then we saw that it was set to 2:20 instead of 112
Hi Walter, I've been learning to program in C, and I enjoy it. The problem is that while I wish to write C, I do want some additional conveniences afforded by other languages. I think the traditional way people go about this is using C++ kind of like a C+. There are also people who use Zig. I'm not a fan of Zig, and I don't like how C++ doesn't always behave like C even when you write in the C style.

How does BetterC stack up to Zig and C++ in this situation? Does it allow me to write plain C but with some of the conveniences of D?

BetterC includes all of the features of D except the ones that require the D runtime library, which means everything except exception handling and operator new and array concatenation.

You've still got full use of metaprogramming, compile time function execution, modules, Unicode, all the memory safety features, etc.

> Does it allow me to write plain C but with some of the conveniences of D?

Heck yeah.

I've had less success pointing out errors to people 'smarter' than me.

One was stuck learning C because they knew just enough about scope to be dangerous. They'd declared 'i' globally and used it in every subroutine for looping. Which failed confusingly when they called one looping function inside a loop in another.

No amount of insisting each function needed it's own 'i' would convince them. I left them to it.

In college, I was writing something in Fortran, and I made a typo. I was trying to call the cosine routine, COS, and I overshot the "oh" key, and typed C0S. It took me two days to figure out why Fortran didn't know what cosine was.
I don’t know how one cannot see the difference between the letter

  O 
and the digit

  0
First, it was supposed to be a capital letter O, because Fortran. Second, this was about 1982, on a green-screen terminal with not a lot of resolution. And third, I was a naive 20-year-old without a lot of experience at what fat-fingering things can do, so I wasn't really looking for that.
Sure. All I mean is, since the time immemorial the zero, on computer peripherals, has been visually distinct from the letter O (which was done by crossing the zero or putting a dot inside it).
Oh, it was visually distinct. Just not very much.
So, you stumble on some high-quality code, very-high-quality code, that has worked flawlessly for decades (and if you don't perceive it as flawless, then convince yourself it's your fault), and you dare mature an idea about modifying it?

Mayhem be on you!

I worked on a code (DOCK for docking drugs into molecules) that had a core routine (a preprocessing step) written in ancient FORTRAN by a genius professor. He couldn't explain why it worked, even though he had written the paper and the code. Everybody who looked at it gave up. Eventually somebody invented an all new algorithm for the preprocessing and wrote it in C++ (comprehensibly) and the old routine was retired, to everybody's satisfaction.
> Familiarity breeds respect

Nice.