17 comments

[ 3.8 ms ] story [ 39.3 ms ] thread
Thank you Captain Obvious!

But easier to optimize doesn't necessarily means that they could be optimized to be more performant than less constrained languages.

But the potential of easy optimization does not mean that they will be optimized. Compilers for higher-level languages are full of broken promises around their potential. "Faster than C because we can optimize" is never really realized, except in maybe one benchmark that only works in that one exact scenario and nowhere in my code...
It's also that whenever an hour is poured into Haskell, Scheme, or OCaml, there's hundreds of hours poured into Javascript, Python, C, Java, ...

Eventually that lets you optimize things more at the compiler level because you have the extra time to maintain it as well.

My primary argument is that a more restrictive language, if designed correctly, has programs which are resistant against bit-rot over time. You can take a program from 15 years ago and compile it. It'll run faster due to optimizations, and hardware improvements. And the program will still be the "right thing to do."

In contrast, if you take C code that's older, you'd shudder and immediately begin identifying things left and right which needs to be redone because the modern machine is different from the aged machine. Part of the efficiency comes from the fact we are building the C code such that it has affordance on current hardware for efficient execution. But when the hardware updates, then the code needs amendment to keep up. Meanwhile, your SQL statement is the same because it's written declaratively.

> Meanwhile, your SQL statement is the same because it's written declaratively.

I must have imagined everything I've read about query planners.

I don't know about "faster than C", but usually the promise is that you can get to within the same order of magnitude of performance with higher level abstractions, more guarantees of correctness (or at least higher likelihood), and for far less effort than the optimal handwritten C equivalent.
Brings back nostalgic memories from Java days in the 2000s. "Java will be faster than C" for exactly the same reasons laid out in the article.

And here we are, 20 years later.

Initial comments as I write this are all negative, and also responding to something the blog post didn't claim. The only time it says faster than C is when talking about a language targeting the GPU; it is not controversial that GPUs perform better than CPUs for many workloads.

On the other hand, the results of this true fact are already seen in practice in real systems. Rust's iterator adapters, for example, allow high level "functional style" with closures to be compiled to code that is exactly the same as hand rolled C loops without unamortized bounds checks - despite guaranteeing the absence of out of bound accesses even in the face of programmer error - because the constraints of these interfaces and the design of the language enable tons of optimizations by LLVM. This has been true since Rust 1.0 more than a decade ago, it's not a promise of the future.

They are also easier to statically analyze and write tooling around.
I have constructed a language which can only say "hello world" and there is only 1 way to do it.

It is the most easily optimized language I know. If you write a working program in it, you know you're doing it in the most optimal way for that language already.

To me, the best argument for constraints imposed by a language is around correctness, not efficiency. When we write a program, we tend to have an idea of how we want it to behave, but it's a pretty universal fact that this is hard to get exactly right on the first try (and often it can be hard to even tell without extensive testing, which is why bugs aren't all fixed by the time software actually gets released). In a certain sense, the act of writing and debugging a program can be thought of as searching through the space of all the possible programs that you could be writing, repeatedly narrowing down the set of potential choices by ruling out ones you know are incorrect, and then eventually picking one to use as the candidate you think is the one you want. From this perspective, language constraints can help with this process pretty much every step of the way; some programs are ruled out because you can't even express them in the first place, others are able to be rejected as you narrow down the set you're looking at based each new line of code you write and how the constraints interact with that, and potentially even with debugging after the fact when trying to figure out what went wrong with an incorrect selection (i.e. one that has bugs).

When we're using Turing complete languages for pretty much everything, constraints are pretty much the only thing that semantically differentiates the code we write in them at all. To me, this is basically the concept people are trying to convey when they argue for picking "the right tool for the right job". At the end of the day, what makes a language useful is just as much defined by what you _can't_ say as exact you can.

I don't disagree, but I think the optimization potential tends to be limited to trivial automations in practice. The idioms we use to code at a higher level are mostly wrappers on something we know how to do at a lower level with a macro. The compiler still helps because it guard-rails it and puts you on the path to success as you build more complex algorithms, but you have to get into very specific niches to reach both of "faster than C" and "easier to code the idiom".

As ever, hardware is an underappreciated factor. We have make-C-go-fast boxes today. That's what the engineering effort has been thrown towards. They could provide other capabilities that prefer different models of computing, but they don't because the path dependency effect is very strong. The major exceptions come from including alternate modalities like GPU, hardware DSP and FPGA in this picture, where the basic aim of the programming model is different.

Reminds me of the talk on Rust's LLVM IR optimization issues. Constrained languages like Rust can be optimized way better than C/C++. Seen similar points made before.
In the same way, I find that constrained languages like Go are a great choice for achieving high-quality code generation with LLMs.
I think a good example here is Unity's Burst compiler - it uses a subset of C#, which allows for more assumptions and better optimization. Compared to regular Unity Mono environment, the code can be faster by one or two orders of magnitude and I've read in some cases it's even faster than C.
I used to argue with the Python crowd about that. In Python, any code can, if it works at it, find and mess with any data object anywhere, even in another thread. So the compiler can't examine a function, decide that it can't possibly do something, and optimize. That's held back PyPy.

Most useful Python programs could be hard-compiled to machine code, because they're not doing anything weird enough to require a JIT compiler. But some low-level library might be, which breaks hard compilation.

("But then my domain-specific language / interface to another language / code generator / tracing package / debugger won't work!")

Everything constrained is easier to optimize. In the limit where you have one or zero possible ways to do something, all solutions are necessarily already optimal.