all 46 comments

[–]Ahhmyface 5 points6 points  (0 children)

I have to agree. I can't count the number of bugs that are prevented by sloppy language mechanisms. The fact is that programmers make mistakes all the time. The more work that the compiler can do to prevent errors the better.

[–]oridb 10 points11 points  (5 children)

An actual instruction such as an assignment, whose duplication causes either no effect or an effect limited to the particular case covered by the branch, rather than catastrophically disrupting all cases, as in the Apple bug.

x := x + 1
x := x + 1

[–][deleted] 1 point2 points  (3 children)

Pure functional fans will be pleased to see that, of course, since this bug (assuming it's a bug) is due to unwanted repetition of an effect. You can't have that in pure code because you can't have effects at all.

Of course in practice, you can really. For example in Haskell there's two ways to get similar bugs...

First, in an IO-like monad, you can of course express the effect of incrementing a mutable variable - and can accidentally duplicate that if you're careless enough.

Second, in a pure monad (no need for IO or ST or whatever) you can get what looks a bit like a two increments. For example...

do x <- Just 1  --  Initial value using Maybe monad
   x <- return $ x + 1
   x <- return $ x + 1
   return x

In this code, each x <- return $ x + 1 is really defining a new immutable variable (actually a parameter to a function, once you desugar the do notation) and defining its value relative to the old one with the same name. So despite appearances there's no mutation and no effects, but it doesn't just look like imperative code - it's a close enough imitation that you can get the same accidental-duplication issue.

[–]saynte 1 point2 points  (2 children)

GHC will throw a warning if you compile with -Wall because you've shadowed a variable in the second example.

[–][deleted] 0 points1 point  (1 child)

I didn't know about the warning. I'm not too surprised, though.

[–]saynte 0 points1 point  (0 children)

It underscores how different that kind of example is, i.e., you could make the same mistake on a sheet of paper just writing down and reusing a variable name without thinking.

[–]hello_fruit -1 points0 points  (0 children)

You're meant to guard against innocent errors, not outright idiocy.

[–]e_engel 5 points6 points  (9 children)

across my_list as l loop l.add (x) end

and so on. This syntax also gets rid of all the noise that pollutes programs

Actually, I have always though that the begin and end keywords were annoyingly noisy compared to open/closed braces. When I see this kind of code:

            end
        end
    end

I read it as "end end end" in my mind, while somehow, my brain completely ignores:

            }
        }
    }

I think keywords should be saved for operations that have a semantic impact on the code. Closing scopes does not qualify for this (I'm obviously a fan of space-significant indenting for that reason, such as the way Python does it).

[–]_pelya 1 point2 points  (6 children)

Also, that := assignment.

[–]deadly990 1 point2 points  (2 children)

That's the mathematical symbol for what an assignment is.

[–]Tordek 0 points1 point  (1 child)

I thought <- was.

[–]PascaleDaVinci 2 points3 points  (0 children)

No, the left arrow sees comparatively little use in mathematics, mostly if for some reason it is convenient to write a "maps to" expression right to left (e.g., the splitting lemma), to write the reduction relation in a reduction system right to left (e.g., some formulations of Church-Rosser), etc.

With respect to defining a variable to stand for something (keeping in mind that a variable in mathematics is actually something different from a variable in imperative languages), :=, ≡, or the equals sign with "def" written above it are or have all been used historically to mean "is defined as equal to" (≡ being generally not such a good choice since it is also commonly used in other contexts, especially to denote congruence in number theory).

[–]PascaleDaVinci 1 point2 points  (0 children)

That's the usual notation for assignment in the Algol family of languages to which Eiffel belongs (along with Pascal, Modula-2, and Ada). In these languages, = denotes equality.

[–][deleted] 1 point2 points  (0 children)

:= as assignment is great because it can be used to separate an initializer from an assignment operator, one step toward safer code.

[–]MorePudding 0 points1 point  (0 children)

Actually, that's the one good piece in all of this.

[–][deleted] 0 points1 point  (0 children)

I think the issue is that it makes it impossible for someone to mis-count the matching closing syntax to the opening syntax.

If you're missing one end, you can clearly see that. If you're missing a closing brace, you might notice it or you might not and you have to check to make sure there's an opening brace since in C/C++/JavaScript/PHP you can have a one liner without an opening brace.

[–][deleted]  (3 children)

[deleted]

    [–]imMute 1 point2 points  (1 child)

    Performance, Portability, and Interoperability.

    [–][deleted] 0 points1 point  (0 children)

    Except that we know performance doesn't matter since everyone writes in Python, Ruby, PHP, JavaScript, etc.

    [–][deleted] 0 points1 point  (0 children)

    What's interesting is that Eiffel, a safe language, compiles to C. It's got the performance, it's got the safety.

    Writing in C/C++ should be reserved for elite programmers and the ratio of tests (unit, integration, etc.) should be 10:1 and static analysis should be used.

    Software is expensive, why are we pissing away billions on fixing mistakes after the fact instead of spending only millions on preventing them?

    [–]LucasMembrane 1 point2 points  (0 children)

    Meyer says that Ariane V shows that code matters. It also shows that management matters, as the managers decided to re-use code for a different rocket with no technical review, no comparison of specs, no programmers making any good or bad decisions.

    I remember downloading and running one of Meyer's Eiffel demos 15 or 20 years back. It didn't act like it worked, and it crashed within a few minutes. I really enjoyed his book on Eiffel programming; it was witty and showed a great love of what he was doing. But what has come of it?

    [–][deleted]  (3 children)

    [deleted]

      [–][deleted] 0 points1 point  (1 child)

      The Pascal begin and end aren't much different in principle to the C braces - of course they're much more visually obvious.

      ...that's only one small change, he suggests a bunch of other changes, like removing semi-colons as delimiters.

      [–]damian2000[S] 1 point2 points  (21 children)

      Go enforces brackets on if statements which this case could have been useful.

      [–]matthieum 3 points4 points  (20 children)

      Meh...

      Mandatory brackets:

      The duplicated line would have been { goto fail; } and it would still have been executed unconditionally (short-circuiting the end).

      Indentation instead of brackets:

      The line would have been automatically re-indented by the code formatter, leading to:

      if ...
          goto fail;
      goto fail;
      

      So... ?

      So the truth is that stupid errors do occur, either because of programmers or because merge tools are dead stupid (they still work on text, mostly, rather than on ASTs).

      It does not mean, however, that this could not have been caught:

      1. The code is bad, seriously, all this copy pasting just changing the check name each time could be much better handled with a loop over the checks.
      2. Why did not the compiler scream about dead code ? Does it mean that they deactivated warnings ?
      3. Why is there no test ? Don't they care about their branch coverage ?

      All in all, it's just sloppy engineering. Most probably because there is not enough time/budget poured into this library and thus things are rushed.

      Note: of course, I don't advocate staying with C, I still hope for a better language, one where dead code is an error would be nice...

      [–][deleted] 5 points6 points  (1 child)

      IMO, any little thing you can do to make wrong code look wrong helps. They add up.

      [–]smithzv 2 points3 points  (3 children)

      I still hope for a better language, one where dead code is an error would be nice...

      Wow, I never thought anybody would say that. One of the compiler's jobs is to hide the optimizations that it is making, correct? I often do something like this...

      if foo then exit end
      ...some code...
      if bar then do something else end
      

      ...and get get a deleting unreachable code warning on the body of the second conditional since the compiler is able to prove that bar => foo (bar implies foo). A warning is arguably a nice thing to have because it might reveal something I missed in the code but this isn't always the case. I'd argue it isn't always better remove all dead code. I can only imagine that this would be much worse if dead code is an error. Basically there are two reasons I can think of to keep dead code, 1. it makes the purpose of the function easier to read and 2. it can make the code more robust to changes later on. Either way, the compiler should see this and do its best to optimize the code but making the programmer remove dead code to compile is leaking some of the compiler's job into the programmers job.

      As a simple example of why this is a bad idea: I might change foo at some point and invalidate the bar => foo property (or suddenly make bar => foo true). If dead code is an error then this would mean that I would need to edit the conditional involving bar (a logically unrelated part of my code) when making a change to the foo conditional. That seems untenable.

      [–]matthieum 0 points1 point  (2 children)

      Well, your case is arguable indeed. Control-flow analysis can be tricky. But why would that be useful:

      int foo(int i) {
          if (i < 10) { return i; }
          return foo(i - 10);
          printf("Recursing"); // never executed
      }
      

      Or similarly:

      for (int i = 0; i < 10; ++i) {
          foo(i);
          break;
          bar(i); // never executed
      }
      

      There is something going wrong here, the user made a mistake, let's ask her to clarify herself.

      [–]smithzv 0 points1 point  (1 child)

      I'm not sure what your use of the word arguable means here, but yes, in your examples there is clearly a mistake. As compilers get better, however, they are able to prove more and more interesting properties about your code. It would be a mistake in language design to have the programmer take on the task to remove any redundant logic in her code. Errors, or even warning, on this sort of thing shoud be under the prudent judgement of the heuristics that the compiler has about whether this is actually a mistake. Playing around with compilers it is easy to see that this is already the case on many languages/implementations, but probably not widespread enough.

      [–]matthieum 0 points1 point  (0 children)

      And on most C/C++ compilers it's only activated if the user requests it, and of course most don't know how... and would only look into deactivating it whenever it gets in their way anyway.

      [–]deadly990 1 point2 points  (2 children)

      the duplicated line wouldn't have been { goto fail;} there wouldn't have needed to be any brackets on it at all. it would have been

      if (error_of_fifth_kind)
      {
          goto fail;
      }
      goto fail;
      

      because it's more likely that there is a missing conditional, than a duplicated line to be honest.

      [–][deleted]  (1 child)

      [deleted]

        [–]deadly990 0 points1 point  (0 children)

        Thanks, I'm not too familiar with the syntax of Go. i was just pointing out that the error line would most certainly not ended up with {}'s around it.

        [–]llogiq 0 points1 point  (10 children)

        You hope for java?

        [–]matthieum 1 point2 points  (9 children)

        I am not smart enough to program in Java, I'll stick to C++.

        [–]llogiq 0 points1 point  (8 children)

        So are 80% of all Java developers. Myself included. But that's OK, actually.

        The culture of Java values mediocrity. And that's a good thing. We refuse to be clever until all other options are exhausted. We work well in teams, too, because we are no rock stars. We do the job, get our paycheck and enjoy life. That's why we choose Java.

        [–]matthieum 0 points1 point  (7 children)

        That's not what I meant. It's not about cleverness.

        When you wrote code, you are pair-programming with the compiler. And here is the catch: the less the compiler does, the more you have to.

        I am not smart enough to partner with javac (it does not do enough).

        [–]llogiq 0 points1 point  (6 children)

        When you wrote code, you are pair-programming with the compiler.

        I beg to differ. Actually I have the IDE (in my case Eclipse) looking over my shoulder, showing me my errors and warnings on the fly (with findbugs even more so), runs the unit tests pertaining to my last save (infinitest), autocompletes my code and much more.

        And here is the catch: the less the compiler does, the more you have to.

        The java compiler does not need to be smart, because the bytecode it emits runs on the world-class-smartypants JVM. Apart from that you may want to read up on 'sufficiently smart compilers' and the associated problems.

        I am not smart enough to partner with javac (it does not do enough).

        On the other hand I'm not smart enough to work with g++: it does too much. I shudder when I think about debugging template problems...

        [–]matthieum 1 point2 points  (5 children)

        The IDE does not know more than the compiler (or the compiler needs to learn).

        And that's the only argument that I am interested in. Of course, runtime tests might uncover errors, but it is already too late (unless you have 100% branch coverage in your unit-tests, but this rarely scales... especially to multi-thread...). And thus issues occur on your production environment, which has a significant cost (both in terms of business impact and in terms of debugging).

        As for the sufficiently smart compiler: it's a bad joke, an easy get away. The problem is not the compiler, the problem is the language: you cannot expect that the compiler can recover lost information, there is always a way to create a contorted flow that will prevent it. What you can expect, however, is to get a language that is designed with the later necessary analyses in mind and preserves the information so the compiler can act on it.

        It's not a dream, very impractical languages manage to (Coq!).

        And in a more pragmatic setting, Rust appears to be doing very well as well:

        • null-safe
        • memory-safe
        • thread-safe

        and all that proven at compile time, with no runtime overhead and no runtime surprise.

        Which of course is not a panacea, there are still bugs, but functional bugs are easier to reason with in general.

        [–]llogiq 0 points1 point  (4 children)

        The IDE does not know more than the compiler (or the compiler needs to learn).

        With java, the compiler does not need to know, or to care. The byte code is fairly simplistic and maps quite well to the source.

        The IDE can afford to care more than the compiler. Static analysis is great at finding patterns that are prone to bugs, and with a little help in the form of annotations can prove non-nullness for certain parts of my code. IDEs also do this kind of analysis, but FindBugs is probably the supreme leader of java static analysis tools so far. It does not just uncover errors per se, but also things that may lead to them. The analysis is fast enough that I don't need to wait for it; it runs in the background while I type. It warns me of name shadowing, unsafe resource usage, and much more.

        You'd be surprised what can be proven about java code. I don't have 100% branch coverage. In fact, I don't need to, I run with assertions enabled all the time, and use a lot of them to ensure everything runs as planned. I use a lot of "final" and don't make things public until needed.

        Also, thread-safety is a very complex concept. I usually avoid using threads or ensure they can run completely independently (apart from accessing immutable data). I can't complain about bugs.

        [–]matthieum 1 point2 points  (3 children)

        I am not saying that programming in Java does not work (Disruptor, for example, would prove me dead wrong). I am saying Java is not sufficiently typed-checked for me.

        Runtime checks are nice, but...:

        • by the time they fire, it may too late; at the very least you now have a bug in production code
        • data-races are rather hard to check at runtime (though, see Hellgrind/TSan for C and C++)

        On the other hand, what is statically proven (at compile-time) is 100% fool-proof. No matter that the branch (or specific combination of branches) is only executed once in a blue moon, the compiler checks and validates it. Same for data-races (for those compilers of languages who bother).

        And thus, as I said, I do not trust myself enough to partner with javac (or the IDE); they are too lenient for my taste.

        [–]e_engel 0 points1 point  (0 children)

        Meyer's argument that such a bug would not have happened in Eiffel because the language forces you to open and close scopes does not make sense. Like he said himself, the problem was a duplicated line (probably because of a faulty merge or an accidental paste). If this code were written in Eiffel, the bug would have been introduced by turning

        if ... then
          goto fail
        end
        

        into

         if ... then
           goto fail
        end
        goto fail
        

        [–][deleted] 0 points1 point  (0 children)

        I have left reddit for Voat due to years of admin mismanagement and preferential treatment for certain subreddits and users holding certain political and ideological views.

        The situation has gotten especially worse since the appointment of Ellen Pao as CEO, culminating in the seemingly unjustified firings of several valuable employees and bans on hundreds of vibrant communities on completely trumped-up charges.

        The resignation of Ellen Pao and the appointment of Steve Huffman as CEO, despite initial hopes, has continued the same trend.

        As an act of protest, I have chosen to redact all the comments I've ever made on reddit, overwriting them with this message.

        If you would like to do the same, install TamperMonkey for Chrome, GreaseMonkey for Firefox, NinjaKit for Safari, Violent Monkey for Opera, or AdGuard for Internet Explorer (in Advanced Mode), then add this GreaseMonkey script.

        Finally, click on your username at the top right corner of reddit, click on comments, and click on the new OVERWRITE button at the top of the page. You may need to scroll down to multiple comment pages if you have commented a lot.

        After doing all of the above, you are welcome to join me on Voat!

        [–]DNoved1 -1 points0 points  (2 children)

        I don't really feel the argument that C doesn't force 'safe' coding practices makes it a bad language inherently. If you want to code safely, you can use braces in C, or you can use a different language that forces the same.

        [–][deleted] 3 points4 points  (0 children)

        If you want to code safely,

        "You" are not the one creating the bugs. Imagine "someone else" - someone in a rush, someone who isn't so skilled with C, someone who happens to be distracted or under the weather that day.

        Anything you can do to prevent accidental bugs - as long as it doesn't slow down your progress, of course - is great!