1. 41
  1.  

  2. 9

    I’d argue that implicit word splitting (leading to “quoting hell”) is a similar mistake in Unix shell. It got enshrined in POSIX so every shell has to implement it.

    It was probably somebody’s afternoon hack (Bourne or Thompson?), and then millions of people ended up using it over 50 years, and also I see about 25 new shell tutorials per year explaining this to new Unix users. It really is amazing.

    Oil is taking a principled approach to fixing all these accumulated problems:

    http://www.oilshell.org/release/latest/doc/idioms.html

    Basically we implement the compatible crappy behavior (which takes forever), and then have an option to move to something better. If you just implement something better, people can’t use it.

    (copy of reddit comment)

    and plug to help me: https://github.com/oilshell/oil#important-we-accept-small-contributions

    1. 2

      I’ve seen it common to criticize design decisions made years ago and write them off as foolish or random, and most of the time it turns out to be lack of undeerstanding.

      1. 7

        I’d just call it “failure by success”… your weekend hack turns into something that people used for 50 years. You can never predict what actually becomes used. Ditto JavaScript, PHP, etc.

        Early Unix often used stuff like char buf[80] for the same reasons, except those kinds of mistakes are fixable.

        1. 7

          it turns out to be lack of undeerstanding.

          Your general point is valid, but the GP here probably understands the nuances of shell, and is in a better position to evaluate their design, than 99.99% of programmers.

          I don’t think this is a case of not appreciating some deeper element of the design.

      2. 8

        To quote Ritchie: “C is quirky, flawed, and an enormous success”.

        This is probably an interesting lesson that you should always be careful in copying what already exists. I’ve seen this quite a bit in product development as well, where people just copy stuff from $competitor without thinking what problem it solves, and if it can be solved in a better way.

        Maintaining C compatibility (or at least, not mindlessly breaking it) is good goal for many languages as such, but some things are probably not worth keeping. I don’t think anyone has ever been confused by the break in incompatibility here for example.

        1. 4

          The article reminds me of how we ended up with <> for generics and how newer languages are still copying this mistake.

          That some languages got it right in case of & precedence is certainly good news; it feels like there is an increasing number of some languages lately that divest from <> too.

          1. 4

            Can you please elaborate on why you believe <> is a mistake for generics?

            1. 12

              There’s nothing wrong with using < and > for generics, the problem is using < and > for generics and using < and > for greater-than and less-than in the same language. If I parse identifier < identifier, am I parsing a generic or an expression. In C++, if you encounter a {, (, or a [, then you know that the program is only syntactically well-formed if (after macro expansion) it has a matching }, ), or ] as the next close-some-kind-of-grouped-thingy character. If you parse a < then you have a lot more that you need to parse to get it right.

              This can lead to some very weird things like std::conditional<a > b, int, float>. This is not syntactically valid but you need to parse the definition of std::conditional and resolve the template instantiation before you know if it’s valid syntax. That makes any other tooling that wants to interact with C++ code (highlighting and so on) is painful. In that example, you can tell that float > is not an expression, but there are more complicated variants.

              In contrast, imagine if the language had used []: now it would be std::conditional[a > b, int, float]. Something with no awareness of the surrounding context could figure out this is a generic expression.

              In Verona, we are using square brackets for generics. We’re also requiring brackets for sequences of different operators, so a + b * c is syntax error, a + b + c is allowed and trivially applied left to right, (a + b) * c or a + (b * c) is also allowed and has explicit precedence. This simplifies parsing if you allow user-defined infix operators (e.g. a and b and c is an unambiguous parse) and means that you are never bitten by not understanding the precedence rules (which really don’t make sense to have if you have operator overloading).

              1. 1

                Sounds like a very reasonable language!

                I have also investigated some approaches to cut down operator complexity to the bare minimum required, but interestingly ended up exploring other options:

                • not having unary pre-/post-fix operators (++, --, +, -, !, ~)
                • not having compound operators (+=, -=, &=, …)
                • reducing the number of binary operators (<<, >>, >>>)

                (Though I would argue that there are two things wrong with < and > on its own:

                • Even if < and > weren’t used for comparisons, that would not undo people spending a decade in schools using < and > with that meaning.)
                • < and > being “lowercase” in most fonts leads to poor readability compared to [ and ], which is “uppercase” in practically all fonts.)
              2. 4

                We have superior solutions that avoid the issues of <> (no sensible allocation of brackets, hard to read, hard to parse¹, …).

                “C++ ran out of usable brackets” should not be the standard of language design these days; fortunately Eiffel, Scala, Python, Nim and Go seem to agree.


                ¹ 30 years after its introduction in C++, no language has managed to parse templates/generics without minor or major absurdities: required whitespace, unlimited look-ahead, intentional syntactic inconsistencies, ::<>, …

            2. 3

              Perl uses the same precedences to make it easier for C folks:

              Operators borrowed from C keep the same precedence relationship with each other, even where C’s precedence is slightly screwy. (This makes learning Perl easier for C folks.)

              Source: perldoc perlop

              1. 2

                The lesson I take away from this is that language designers should avoid making changes that break backward compatibility. This is definitely true for C#; there have been few breaking changes and some compiler bugs have remained unfixed because they would break backward compatibility.

                1. 4

                  I don’t think that’s the lesson the article teaches us, which is that not fixing mistakes due to “backward compatibility” can have unexpected, incredibly broad long-term effects.

                  I’d say that 80% of the languages today that carry C’s bug didn’t copy it because of fAmiLiaRiTy, but because they were completely unaware that it was a mistake in C in the first place.

                  1. 2

                    I don’t think that’s the lesson the article teaches us, which is that not fixing mistakes due to “backward compatibility” can have unexpected, incredibly broad long-term effects.

                    I agree that backward compatibility can have unexpected long-term effects.
                    Where we disagree is that I think backward compatibility is often required for mainstream success.
                    It’s difficult to introduce a new language and people are often reluctant to learn something different.
                    I think the following quote accurately expresses this issue.
                    “It was clear from talking to customers that they all needed GC, JIT, dynamic linkage, threading, etc, but these things always came wrapped in languages that scared them.” – James Gosling
                    C-style syntax is problematic but I don’t think that C++, Java, or C# would have succeeded without reusing it.

                    1. 1

                      What’s missing is a grown-up, educated-by-past-mistakes and well-implemented process between

                      our compatibility approach is to never ever change anything and totally ignore how even Java gave up on that (Rust)

                      and

                      wAt iS cOmPaTiIbiLiTy? cAn I gO bAcK eAtiNg CrAyOns iN tHe cOrNer? (PHP)

                      We have all the tools required to pull this of, we just have to stop LARPing “language development in the 1960ies”.

                2. 1

                  Python isn’t my goto lang but I checked it, yep. It behaves differently. You’d also not use 0 or 1 as exit code style truthy values. But the precedence is still different than javascript.

                  // the js version
                  let x, z = false;
                  let y = true;
                  
                  let r = (x & y) == z;  // r is true
                  let s = x & (y == z);  // s is 0
                  let t = x & y == z;    // t is 0
                  

                  That’s surprising but not in the normal JS beating way but I guess historical context? Neat, TIL.

                  1. 1

                    I’ve always believed precedence should just be left-to-right. Like, whatever, right?