1. 15
  1.  

  2. 9

    today’s languages contain far too many features

    Totally agree on that – it doesn’t even need qualification.

    Things I’d rather not see in new languages:

    • if-then-else and ternary operators and switch and …
    • unary operators
    • half a dozen ways to write number literals
    • static keyword
    • semicola
    • generics with <>
    1. 4

      But then it wouldn’t look like C, and no-one would adopt the language! /s

      1. 0

        True, I’m waiting for the angry troll, unkind and incorrect flags to drop. ;-)

      2. 1

        if-then-else and ternary operators and switch and …

        I personally never use switch in C, but I do use ternary operators occasionally (although they can get messy).

        unary operators

        I agree when it comes to increment/decrement. I always i = i + 1 for clarity.

        Then again, how else do we get the one’s compliment (~)? Address of (&)? De-reference (*)? (If you’re answer to the last two is there are no pointers, then get off my lawn)

        half a dozen ways to write number literals

        If you are writing constants that need to have specific bits set, it helps to have hex/octal literals.

        The 1.0f thing for denoting single-precision and other things like that are useless to me. Modern compilers are able to infer typing for literals.

        static keyword

        Agreed.

        semicola

        How else do you terminate a statement? If you want to force one statement per line, that’s fine. But then, what if I want a multi-line statement?

        generics with <>

        I personally don’t care about generics at this point in my career. I’ve never used them in production software.

      3. 5

        where computation is almost free, memory size is almost unlimited (although programmers’ ingenuity in creating bloated software apparently knows no bounds)

        I would submit that using bignums (or the local equivalent) everywhere, when fixed size types will do, is an example of “programmers’ ingenuity in creating bloated software”.

        This:

        Compromises to minimize instructions extend so far as to make familiar-looking operators like + and < behave in unintuitive ways. If as a result a program does not work correctly in some cases, it is considered to be the programmer’s fault.

        should be addressed in new languages by having + and < stop the program if they’re about to do something “unintuitive” as opposed to removing bounds everywhere, IMO.

        Maybe I’m not yet understanding the author’s argument, though; I’ve only read the page linked, not the rest of the work.

        1. 2

          I would submit that using bignums (or the local equivalent) everywhere, when fixed size types will do, is an example of “programmers’ ingenuity in creating bloated software”.

          FWIW, Haskell takes the other approach: every number is a Num, which is a very abstract type, but also possibly an Integer, or maybe a fraction, or a Float. The compiler infers the type based on what operations are performed, but the programmer can specify the type to ensure more efficiency.

          Basically, instead of getting fed up and saying “bignums everywhere”, use “numbers everywhere”. This can also lead to bloated software of course, so give programmers the tools to mitigate that.

          1. 1

            Let us consider something simple:

            9999999999999999.0 - 9999999999999998.0
            

            What can we actually do about this?

            • atof() could fail since the string representation doesn’t match the resulting float, but then what should happen for 0.3?
            • We could use a hypothetical atonumber() which uses decimal or big float or some other representation, but what does this do to performance?
            • We can ignore the issue and blame the programmer for not constraining the input domain to that of our function. This is what most people do.

            I’m interested in other solutions. My current best idea feels extremely ambitious and would not be easily ported to other languages and programming, but it seems right, so is it worth being unpopular?

            Popularity is an important function of culture, and our “culture” is extremely resistant towards unpopular solutions, but now consider this: If the language and tooling contributes to faster, shorter, and more correct (i.e. produces the “right” answer for a larger input domain), then isn’t that better?

            For most programmers, the answer seems to be no: For most programmers having the right amount of whitespace and the ability to use sublime text and stack overflow is more important. That’s a shame, and I think it really makes it hard to talk to other programmers about just how much better programming could be.

            1. 4

              Popularity is an important function of culture, and our “culture” is extremely resistant towards unpopular solutions, but now consider this: If the language and tooling contributes to faster, shorter, and more correct (i.e. produces the “right” answer for a larger input domain), then isn’t that better?

              Massive snark warning: popular programming culture is the intellectual equivalent of Medium: neither rare nor well-done. Pop culture is fashion, and gets dragged kicking and screaming to each new idea, which it then slowly accepts. The worst part is it then rewrites the oral history to argue that it always saw the brilliance of the aforementioned idea. In this way, it is never wrong about anything, and, implicitly, all the things that aren’t popular can’t be that good.

              1. 3

                We could use a hypothetical atonumber() which uses decimal or big float or some other representation, but what does this do to performance?

                Not only that, it just shifts problems around. With decimal, what happens when you compute 1/7? With bigfloat, what about 0.1? With rationals, what happens when you have iteration on an expression with relatively prime numbers? With any representation, what about irrational numbers?

                Fundamentally, we’re just trying to figure out how to represent an infinite amount of precision in a finite amount of space. The meaning of sufficient precision is application dependent, and there are tradeoffs.

                Better knobs and more types that let us pick tradeoffs may lead to more precise results, but it doesn’t move us to easier.

                And given that people don’t do error analysis today, I suspect it won’t happen in this alternate knob filled universe either.

                1. 1

                  Good point. I was reading it through the lens of my own experience. That made me think of integer overflows/underflows when I read “ + and < behave in unintuitive ways”… and my answer in that case for the programs I write is that I’d like them to fail rather than just silently give incorrect results.

              2. 2

                memory size is almost unlimited

                I’ve started working on Next Generation Shell at 2013. My impression that GC pressure was and still is an issue. Therefore, NGS does have “primitive” types which fit into 64 bits and pass-by-value as opposed to “regular” heap-allocated / passed-by-reference. At the time I checked, Ruby did something very similar. Judging by documentation and how things work - Java is the same. I assume many other languages also did that.

                The main issue I experienced with NGS is that these “primitive” values can not have metadata associated with them as opposed to “regular” objects. For example fetch() method can read JSON from file. I would like to attach metadata to the read object - the file name. Then store() method instead of having mandatory destination can have optional destination and store(myobj) would save the data into the file it was read from. Unfortunately it breaks because the file could contain valid JSON such as 1, which would deserialize into the “primitive” value 1.

                I am wondering whether it’s a good time to revisit these considerations (regarding GC pressure and memory usage).

                do almost the same thing but have slightly different performance characteristics

                Trying to avoid this in NGS.

                Furthermore, the misleading theoretical simplicity of “encapsulated” objects or abstract data types implies regarding methods as belonging to classes and having implementation entirely in a single class

                CLOS doesn’t do that, Next Generation Shell doesn’t do that.

                1. 2

                  Ha ha, I just got it: the language is called Lunar because it’s David Moon’s.

                  Nice to see him continue to work on it. I owe him a great debt to unlocking my mind on what a PL can be.

                  1. 2

                    Thus in most programming languages certain data types such as numbers are a special case which does not fit well into the general type system of the language, and hardware details such as the number of bits supported by an integer add instruction show through in the language semantics.

                    It does not reflect the realities of modern hardware, where computation is almost free, memory size is almost unlimited (although programmers’ ingenuity in creating bloated software apparently knows no bounds), and the principal limit to performance is the cost of communication. For example, one cache miss might take as much time as a hundred add instructions. If it does not noticeably increase the size of the data or program, quite a large amount of extra run-time computation can be added to most programs with no effect on their total running time. This computation can be invested to give the programming language a more rational semantics and to remove common sources of hard-to-find errors.

                    I think this is just inconsistent. If you want to reduce cache misses, you’re going to have to care about how tightly you can pack your data.

                    Maybe there’s some way to get bigints in most cases, with the option to drop down to small types like i32/u32 where necessary, but I have the feeling it would end up being inconvenient in exactly the ways the author dislikes.

                    1. 1

                      It does not reflect the realities of modern hardware, where computation is almost free, memory size is almost unlimited (although programmers’ ingenuity in creating bloated software apparently knows no bounds), and the principal limit to performance is the cost of communication.

                      Well, I copy-pasted this with the intent to argue against it, but after a second thought I think he’s right. For my graduation I actually did a project where it turned out to be faster to recompute a sparse matrix from scratch than to load it from memory (granted, the computations where done on an FPGA, so that helps).

                      When people optimize programs a lot of their mental effort goes into:

                      • What is the fastest way to do this? Should I store intermediate results, use the GPU, use multiple threads?
                      • Woah, programming/concurrency/GPU’s are hard! Is this actually correct?

                      If you could specify a computation from a high level to a compiler with a approximate model of the costs of GPU offloading, spinning up a thread, etc., it might be possible to write optimized programs much faster.

                      1. 2

                        If you could specify a computation from a high level to a compiler with a approximate model of the costs of GPU offloading, spinning up a thread, etc., it might be possible to write optimized programs much faster.

                        …and the performance would vary vastly based on version of compiler, version of runtime, target platform, etc, and it would be great until you have to trick the optimizer to doing what you actually wanted or it suddenly doesn’t work on someone else’s system for Mystery Reasons. The idea’s not without merit, but there’s a lot of complexity there.

                        1. 1

                          I’m not denying that. I’m just observing that a lot of effort goes into implementation details.

                          A lot of the complexity stems from dealing with a non-fixed environment and there is no easy way to avoid it.

                        2. 1

                          If you could specify a computation from a high level to a compiler with a approximate model of the costs of GPU offloading, spinning up a thread, etc., it might be possible to write optimized programs much faster.

                          If such language exists, what would it look like? Maybe something like Prolog or SQL?

                          1. 2

                            I’d imagine some mix between OpenCL and Haskell would work. You’d need annotation to indicate expected values of variables. If you’d loop through something 10 times it’s probably not worth it to put it on a GPU. If you do it 10 billion times and it’s highly parallel, it might be worth it.

                        3. 1

                          I think there’s a spectrum of performance in design, and I don’t think anywhere on it is “wrong” in this way. It depends on what you need from the language.

                          The elder languages and their descendants, that have absorbed ancient hardware limitations into their data types, can give you great performance but require you to explicitly write high-performance code if you want it. Obviously this can be tedious, and pushing performance concerns into the program domain means programmer effort is multiplied globally, but I can appreciate that target hardware and performance requirements are part of the problem domains for which a language is designed. A web server is very different to a videogame, and these languages can work on the assumption that you’re unlikely to share code between different types of applications, so it’s okay to make the programmer choose between a cache miss and a recalculation when they know how everything will cope. Explicit is better than implicit in this school of thought.

                          The other end of the spectrum essentially treats compilers as magic. This certainly is freeing when you want to focus on the bigger picture, but sufficient magic can take a lot of effort on the part of the compiler writer, and it’s not always obvious how to convince a more magical compiler to output the right kind of code. Everything’s a trade-off.

                          1. 1

                            memory size is almost unlimited

                            This is both mathematically false and in practice false. The entire article is based on this premise.

                            Memory is a limited resource. Memory allocation can fail. Any language that denies this fact cannot be used for 100% reliable software.