78 comments

[ 4.6 ms ] story [ 148 ms ] thread
Hmm...BCPL isn't from the 50s. C++ isn't from the 70s.
yeah that was my immediate thought, stopped reading at that point.

BCPL was from the late 60s, most people were introduced to it in the 70s, C++ from the mid-80s

(comment deleted)
Yes, and Smalltalk certainly hasn't "added dynamic features to Algol".
Nitpicking. Fortran was designed in the fifties, the precursor to BCPL was designed in the early sixties, and C++ (C with classes) was originally designed in 1979, heavily influenced by Simula which also dates back to the sixties.

The time of the design of the language is different from actual implementations being made available. C++ compilers were pretty much unusable until the nineties.

> For example, one cache miss might take as much time as a hundred add instructions.

> Today's languages contain far too many features that do almost the same thing but have slightly different performance characteristics, for example regular and virtual functions in C++.

Odd example. Virtual functions help enable certain designs, but avoiding virtual functions is commonly recommended when writing cache friendly code.

Honestly it really hurts the author's credibility.

Virtual functions hurt performance (generally not significantly) but allow for OOP design, in theory to make the program structure more clear/organized. Exactly what the author is supposedly advocating.

I wonder what the author would do, when performance is critical. Today too many people rely on fast cpu and large memory. There are lots of applicants, where you need to optimize.
Is there a cached version? I could not find it anywhere.
Pretty vague and empty argument (rant?). Doesn't really say anything I haven't seen complained about (and disputed) elsewhere and doesn't really propose any actual solutions.
Computation is not almost free and people are doing just fine writing slow code without the language helping them.

Taken that we've more or less stalled with regards single core performance, I'd argue that computers are now getting slower (because software is bloating faster than single core performance is increasing).

> Computation is not almost free...

In the vast majority of cases, it is. He's talking about the ALUs actually performing work, as opposed to waiting on memory. He rightly points out that cache misses make the biggest performance impact on modern systems. Most languages these days do not address this, there's tons of indirection by design.

> Taken that we've more or less stalled with regards single core performance, I'd argue that computers are now getting slower (because software is bloating faster than single core performance is increasing).

Again, if you can actually give your ALUs enough work to saturate them, your problem is most likely embarrassingly parallel, in which case single-core performance is not the bottleneck. Otherwise, you will want to optimize for memory access.

What a bunch of bs. There are so many places where computation is far from free and size matters (embedded, HPC for example). And the end of Moore law is just gonna emphasize the need for performance even more. This article just tells me that the author didn’t have to write any performance critical code in his life and he just extended his experience to everything
> There are so many places where computation is far from free and size matters (embedded, HPC for example).

Even in more typical use cases, it matters: for example performance or memory use can mean the difference between a small or large EC2 instance which has very real cost implications. Or it affects battery life of my phone app. Or between my application feeling fluid or laggy.

All of there’s things have real implications for users.

I very much care when I need a top of the line computer to run some shitty application that should be able to run on 20 year old hardware.

Yet I also understand programmer ergonomics and recently implemented an interpreter for a domain specific language in an already not the fastest language because productivity mattered more to me and correct easy to write domain code mattered more to me. But the point is that it was a conscious decision made after weighing the costs and benefits, not just because I think compute and memory are free. They’re not, this has a real cost for me being able to run it, in terms of servers, but with the benefit that I can replace complex buggy code that took me months to write, with simple easy to understand code that I could write in a few hours (and a thoroughly unit tested parser and interpreter that took a week to write)

> There are so many places where computation is far from free and size matters (embedded, HPC for example).

The amount of code where the cost of actual computation isn't vanishingly small relative to memory access is so tiny, it arguably should not dominate the design of a general purpose language.

This wasn't true forty-plus years ago, when these languages were designed, and it's not true on some microcontrollers, but it's generally true today.

The author is completely right in that general purpose languages do virtually nothing to optimize memory access (which is a hard problem), as opposed to optimizing ALU usage (which is relatively easy).

Yeah, what would David Moon of all people know about performance critical code.

https://www.h2o.ai/blog/a-brief-conversation-with-david-moon...

It doesn’t really matter who wrote it. It’s a comment about the article , not the author himself and the article is approssimative at best. It makes a statement that’s not true in a lot of cases (“computation is almost free”), makes some statements that while agreeable are too high level in how they are expressed like “cost of communication is high” and then the piece ends without any explanation or examples on what he means exactly or what is wrong and how to solve it in enough details to actually be an informative article. It seems like an oversized rant a programmer would make after a bad day at work on Twitter.
Each programming language is suitable for solving certain type of problems. So when I see a catch-all statement like "All Programming Languages are Wrong", it seems to me the author is implying that "None of the programming languages solve my (or this specific type of) problem."

> It does not reflect the realities of modern hardware, where computation is almost free, memory size is almost unlimited ...

Well, no, this certainly doesn't apply to all problems!

> ... because they seem to be in love with the idea of "object" or "abstract data type." ...

Again, there are many different paradigms of programming, OOP being one of them with its own pros and cons.

IMO the biggest problems with almost all popular programming languages are

1) null

2) exception based error handling

such that, when you call foo() where foo is

String foo(){ blabla }

you can get a String, null OR an exception (!!!) and most compilers happily let you treat it as if it only ever returns a String.

I hope some day null is no longer a thing, and that Functional Programming types like Option, Either, Try etc in the native libraries is the new default.

Ive thought a bit about this, and how is Option types (which I like and prefer to error checking) really different from the compiler emitting an error when you fail to explicitely check for nulls and stdlib returning nulls when you have one ?
Because it forces (or at least strongly encourages) you/users of such a stdlib/API to explicitly think about and handle the null case and the non-null case if you want to access the contents directly / unwrap. At least that's the idea.
That is what it is, a branch at the type level that the type checker mandates you complete both sides of.
I am probably in the minority here but I think Java was on the right track with checked exceptions.
I used to think so, but

* Java also has unchecked exceptions, and, incredibly, there's still no consensus as to which exceptions should be used for what and when, though these days the most common approach is to simply stick only to RuntimeExceptions, which is terrible.

* Functional error handling using Option, Either, Try etc are arguably much simpler, safer and more powerful than exceptions.

Simpler because they don't rely on dedicated syntax- they're just regular objects no different to any other object.

Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more. (no risk of ignoring errors and no risk of catching a higher level of error than desired, ubiquitous bugs in exception based error handling)

Powerful because they support map, flatmap, applicative etc, making it easy to eg chain multiple computations together in desired ways, which is unwieldy and bug prone when using exceptions.

It could be that, when learning Java, Python and any other language, we learn that methods return objects... and that's that. No weird dedicated syntax and magic, special treatment for returning anything other than the happy path, and the HUGE complexity that comes with it, eg the dedicated syntax itself and how it behaves, differences between checked and unchecked exceptions, hierarchies of exceptions etc etc.

When lists and booleans also implement map, flatMap etc, you can actually reduce syntax of languages even further- there's no need for looping syntax like for, while etc, and no need for if else either. This is probably too extreme for most people, but think about it!

Slightly off topic, but what should a map or flatMap implementation on boolean do? Presumably run on true and skip on false? That’s not really what I expect from map (that it’s filtered the input). I could imagine book.filter.map as a way to conditionally execute
Haha you're putting me on the spot here, I guess I haven't really thought too deeply about the specifics!

Maybe flatMap would return true only if all chained booleans are true?

Bimap would allow you to provide functions for what to do in true and false case?

Fold would allow you to turn true or false into a single type?

I'm sure someone more experienced in FP can weigh in with something more correct! :p

I rather like that, `if condition` is a 0/1 iteration with no input. flatMap can return empty, what should map return when run on false, None?
I'd probably think of Bool as being isomorphic to Maybe (), which would mean your map implementation looks a bit like:

    map :: Bool -> (() -> b) -> Maybe b
    map True f = Just (f ())
    map False _ = Nothing
Or, in Haskell because you've got laziness you could go with:

    map :: Bool -> b -> Maybe b
Which already exists as a combination of Control.Monad.guard and Data.Functor.(<$).

    map tf b = b <$ guard tf
Checked exceptions really aren't much different from dedicated return type for errors. The Either type in Haskell and its Monad is really not doing anything fundamentally different: Bubbling up errors until the point where someone cares/needs to handle them (though I have to say I hate the whole Left / Right convention thing, just define a dedicated type with proper constructors for value vs. error already).

In fact I'd say checked exceptions are a richer formalism since they essentially provide something akin to intersection types for errors.

The fact that there are also RuntimeExceptions comes up a lot as an argument against Java/checked exceptions but that doesn't convince me. Haskell also has runtime errors that can happen (division by 0) that are outside the Either or Option/Maybe world. The problem is maybe rather that people are lazy and the trend became to decide that everything should be a RuntimeException because who needs all that noise and checked exception handling, right? I'm fairly certain that people who rejected checked exceptions would also reject the more functional approaches.

My argument is that no consensus around using checked exceptions correctly ever developed and so they never really had a chance to shine.

Having said that, sure I think having more usage of idioms we find in FP for error handling/propagation or early aborting is great. It's better than just raising runtime exceptions. However we are still in a world where no widely known language actually enforces (!) error handling in that fashion. It's all convention in the end.

And so my point was that if you want a language that tries to encourage error handling, with known failure modes communicates in the method signature then Java's checked exceptions sound pretty close.

Sure they are not perfect but the question is whether it's worth exploring better formalisms along the lines of first class errors with dedicates language support or piggybacking on the existing type system features and introduce conventions? The latter sort if works but I find it less ...exciting.

  stream.map(f).collect(…)
is where checked exceptions failed. #map is a generic method that should obviously throw each of the checked exceptions f can throw, but without some kind of generic sum type Java can't express that. Everyone is forced to choose between "can throw any exception" (e.g., Callable<T>) and "can only throw unchecked exceptions" (e.g., Function<T, U>), each of which spreads virally and punishes anyone using specific throws clauses.
There is no need to get rid of checked exceptions to fix that.

You need generics for exceptions. If the inner function can throw some exception, then it should be possible to declare the stream method that it will throw the same exception.

I believe that the JVM doesn't actually support this, so the reasons were got the horrible situation we're in is because they didn't want to sacrifice backward compatibility.

I think we're saying the same thing. If f(T) can return U or throw A or B or C, there needs to be a way to declare that Stream<T>#map takes a Function<T, U, A|B|C> and returns a Stream<U> or throws A or B or C. But that sum type A|B|C doesn't exist in Java if there's no common base class.
Something that Java typically gets the blame for, but was actually introduced in CLU, adopted by Modula-3 and C++ before Java was even born.

And with my .NET hat, I hate having to dig into outdated documentation to discover what exceptions might come my way, so one ends up catching any kind of exception, just in case it might blow, in critical code paths.

AFAIK no C++ does not have checked exceptions like Java does. There's no static check enforcing that a caller needs to handle (or also declare throwing) an Exception declared to be thrown by the called method.
Coming from C# to Java, checked exceptions are the worst. There are so many layers that can fail even in a simple API call - network, database, permissions, memory, etc.. We're not going to handle each of those cases differently, if we were going to we could always catch the specific exception type explicitly. But usually it's fail the API call, catch the base exception, and report the relevant failure message. Fail fast, report, move on. Maybe there are other programming situations where you want to catch every possible type of exception and run some custom logic for each one? I'd be interested to hear about it.
My point was that if one wants a language that is explicit about what error types a method can cause (see the top-level comment I replied to) then checked exceptions aren't far off and in fact on the same track.

The problem you describe may be an issue with checked exceptions but it would equally be one in any language with the aforementioned feature.

> We're not going to handle each of those cases differently, if we were going to we could always catch the specific exception type explicitly.

But you don't have to handle them differently. You can though and your method signatures will tell you what type of errors you might have to deal with. Some make sense to handle on the spot others don't.

You don't get that with runtime only exceptions. With those you really don't know and can get all kinds of errors and so you expect all and then just catch all and deal with it or blow up/abort whatever is going on (which let's be honest is what most web services do). Is that good? Yeah, sometimes. It certainly is easier to do that instead of thinking about possible failure modes when you know you'll only end up logging the error and give up anyway. I'm not trying to be cheeky, it really is overkill to make full use of checked exception in some cases.

Granted I haven't seen many great examples of checked exception usages so far but I wouldn't dismiss them because they don't mesh with what you're used to or with what's common in a given domain.

Always hated checked exceptions because there seems the basic assumption underlying them, that exceptions can usually be handled as near to the code throwing it as possible, while in every real world project I ever was involved in, 90% of the possible exceptions usually had to be handled "far away".

So this means, you will have a whole bunch of re-throws, or exceptions wrapped in "higher design level" exceptions which you then throw to finally communicate the error condition to the proper component, polluting all signatures on the way with "throws".

Or you catch it as early as possible, wrap it in a RuntimeException, re-throw it unchecked and let the high level component properly handle it, effectively bypassing the "checked exceptions" system :D

Just my two cents, I know this might be a controversial position.

I think the problem with checked exceptions was that we got a woefully limited language for talking about them. Checked exceptions with generics (and inference?) might be a very different experience.

For instance, Java's checked exceptions break higher-order functions. A map function should be able to say "I can throw anything my argument can throw". Some wrapper functions might say "anything my argument can throw, minus FooException because I catch it, plus BarException because I might raise that myself."

With functional error handling, you don't need neither checked nor unchecked exceptions, everything just returns objects, and the language and syntax used for talking about them is the same as the language and syntax used for everything else (map, filter, flatMap, fold etc etc).
I was speaking to checked exceptions vs unchecked. "Exceptions at all" is a separate question.

If we're going to dig into it, first we need to call out the occasional need to interrupt running pure code (eg. "the user hit ctrl-c"). As it can happen at any point, I think it's uncontroversial to say that return values are inappropriate? These are Haskell's "asynchronous exceptions", I think they do their job fairly well, and I don't know of a compelling exception-free alternative (although I'd be very interested to learn).

So now we can restrict ourselves to "synchronous exceptions" - those raised because of the function we've called.

I agree that we don't need them. They bring to the table early termination and transparent error propagation. There are contexts where these are desirable - if you disagree on that point let me know and we can dig in. In sufficiently expressive languages, we can do it ourselves - so that's plenty to show we don't need them.

But we may want them.

When we do it ourselves, we usually lack a way of telling the compiler which paths to privilege, and we often find some overhead compared with native exceptions.

When exceptions are provided in the language, we get special syntax to distinguish them. You describe this as a drawback, but nonuniform syntax can help with legibility - if different things look different, I can more easily figure out what I'm looking at. Obviously there are tradeoffs here.

Putting exceptions in the language also provides a distinction to the compiler in type checking, allowing different rules to apply. For instance, Java methods throw some set of exceptions, which get unioned (&c) in ways you can't speak about sets of types otherwise. Simply applying the ordinary value-level rules would be problematic (I hit this in Haskell sometimes). Ideally you can broaden your type rules to support what you need for all of it in one system, but it's not always clear they combine well. Even if it all can be made to fit, I could easily see a situation where assumptions need to be made for inference to work, and the most ergonomic assumptions are different for most values vs. bags of errors.

With functional error handling, every method just returns an object. If the caller doesn't want to do anything with it, it can just pass it on somewhere else. Only once you actually care about filtering, mapping, flatMapping, folding etc do you actually need to handle the different cases.

It's just so much better and easier to understand than exceptions! Doesn't require any special syntax or mental gymnastics to understand the execution path either.

I fail to see how this is different.

Say you have a function f1 returning an Either a b value then another method f2 which calls f1 but doesn't handle the error case will simply flatMap or bind or whatever the language of choice is calling it and return yet another Either a b value until at some point up the call hierarchy someone actually decides to unwrap and if necessary do something with the error case. The function signatures have the same problem as the parent comment mentioned.

flatMapping over an Either and returning the result is no different than adding "throws SomeException".

Then Option types (since they were mentioned as well somewhere) are a bit of a different story all together. They make sense for describing absence/presence if values/results and are definitely useful. However they don't compete with Exceptions in any real way since they don't really work for error handling beyond the most basic cases (i.e. where handling the error somewhere doesn't matter/need to happen).

Is this just my feeling or this this text completely ignore languages which are of functional / declarative nature? Iirc Prolog doesnt "care" so much about performance (on a language level at least). Haskell knows Integer, Rational, Num etc. Same goes for Idris Languages from that area (lambda calculus) are, by definition, not based on the idea of some specific computer architecture. Totally possible that i missinterpreted something tho.
> Most current-day programming languages seem to be based on the idea that computation is slow, so the user and the compiler must work hard to minimize the number of instructions executed.

Instructions executed is not the only metric to consider! It doesn't makes sense to have loads and stores in the program with all of them having a cache miss.

Does the author mean that the memory hasn't caught up with the computation here? I don't think so.

> For example, one cache miss might take as much time as a hundred add instructions.

This is why compiler must work hard!

> Today's languages contain far too many features that do almost the same thing but have slightly different performance characteristics, for example regular and virtual functions in C++. This just encourages programmers to waste time on micro-optimization when they could instead invest time understanding the large-scale behavior of their program and optimizing that.

Not all programs need to worry about insane performance. But most do. It is totally upto the developers to choose the features of the language which they need. Choosing the right features/language for the right kind of programs is an important skill. Efficient code generation for all possible hardwares and all possible workloads with a restricted language features is not going to work or else Python would have been one of the very few languages to survive. Elon Musk wouldn't have tweeted that he needs C/C++ engineers ;-)

It seems the article suggests that neither the hard working compiler nor the hardware friendly language is required to get good performance!

There's also energy consumption. Maybe the inefficient implementation isn't noticably slow to me, but i'll be unhappy if it drains my battery.
Yes, there are so many things to consider. Another would be size of the binary..

Different kinds of problems are solved by different languages. Simply rejecting them without stating the details doesn't fly!

Does anyone else consider these type of articles the clickbait of the programming world? They tend to all follow the formula of

1. Make over the top statement in title.

2. Vaguely address the over the top statement in content.

For example the author hasn't shown us why "All" programming languages are "wrong". They haven't shown how they would fix them to make them "right". Their premises (e.g. "computation is almost always free") are flawed.

I wonder, if the title was called "A rant about some popular programming language paradigms" would it be as popular?

Agreed. Between Lobste.rs and HN its becoming increasingly difficult to filter out worth-while content.
> filter out worth-while content

Isn't that the opposite of the goal?

"computation is almost free" actually triggered me. that's the java argument from 20 years ago, now we have go and rust. it implies electron is actually a sane approach to ui development.

the author is very vague about why exactly numbers don't fit in the type system, and in which languages. first class types for numbers exist in some languages. is he advocating for default overflow checks, which bloat code size?

> the author is very vague about why exactly numbers don't fit in the type system

No, he’s quite specific: “hardware details such as the number of bits supported by an integer add instruction show through in the language semantics.”

Now, it's also true that there are quite a lot of languages where this is not true, e.g., Scheme, Haskell (IIRC with the default numeric settings), Ruby, Python, for instance, and many more have numeric types in core language or stdlib that don't face this problem but don't make them the default for simple numeric literals.

> the author is very vague about why exactly numbers don't fit in the type system

Numbers are hard!

Consider something simple:

    9999999999999999.0 - 9999999999999998.0
In discussing what is Right™ we are making a choice between all the possible Good™ ways we can deal with that expression. Here are some of the most obvious:

1. atof() could fail since the string representation doesn’t match the resulting float, but then what should happen for 0.3? I've seen this only occasionally.

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

3. 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 don't personally like any of these; I've seen and experienced some of the problems with each of the approaches, so I wonder if there's a Good™ fourth option (or a fifth). Maybe it's only because I have my own ideas of what a fourth option might look like that I'm not very quick to consider this a "solved" problem to the point that someone (anyone) knows the Right™ answer, but maybe it's worth you (and others!) thinking about this too.

I'm extremely interested in suggestions here.

"computation is almost free" - don't say that around the accountant at my company responsible for paying our AWS bill!
I simply agree with what you said. "Computation is always free" made me cringe as well. I also felt that the initial page is abruptly stopped without giving any kind of explanation.

Then clicking next you get jumped into a new language implementation : /

The statement was "almost free", which is much more defensible than "always free", even if it doesn't fit every context.

If we're limiting what we mean by "computation" to a few extra non-branching instructions on data we already have, I would probably even endorse it outside particularly unusual settings.

It is not an article but the annex of a complete book, whose author was not arrogant enough to make this text the introduction. Nonetheless I agreed with most of the points and saved the URL of the table of content for later review.
While computation might be sometimes pecuniarily cheap, it is often environmentally costly.
He claims all programming languages are wrong and then he bashes C++ virtual functions vs regular functions, C++ templates, single dispatch and fixed precision arithmetic. Looks like "all languages" means C++ to him.

There exist languages with arbitrary precision arithmetic, e.g. Python or languages with much sophisticated polymorphism e.g. Haskell, Scala or Rust.

Or Common Lisp, where you not only have arbitrary precision by default. You also have rationals.
Yeah exactly, we all know that the most used programming languages like Python and Javascript are way too focused on performance.
I can't agree about the dot.

Dot makes it easy to chain things left to write, and also almost always dot based syntax is more readable than stuffing all arguments in parenthesis.

xs.delete(x)

vs

delete(xs,x)

And virtual / regular function distinction exists in performance centered languages for a good reason. Either you have to go out of your way to prevent dynamic dispatch or use clunky function pointer syntax and manage vtables manually. Similar can be said to many features he complains about.

The author has a good point, although he miscommunicates it: The common use-cases, such as servers, business logic, glue code, etc., don't usually require hyper-optimized performance. It only needs to be reasonable and predictable.

They need a language that is easy to work with, and share with others, and there are few that really do that.

But of course, there are. Go, Python, Julia and others I'm sure, all prioritize comfort over performance. Although, as compilers improve, they might one day actually become faster -- since the user writes more abstractly, there is a wider search space for optimizations.

> But of course, there are

Your statements are contradictory.

> they might one day actually become faster

faster compared to C/C++ ? Of course, Performance is improving day by day. What's the point?

It's not contradictory. I'm saying that the things he says are true, and there are many languages that follow that line of thought.

Yes, faster than C++. Better abstractions can, sometimes, give the compiler more flexibility to optimize.

Julia does not prioritize comfort over (runtime) performance. The entire language is based around developing clever ways to get zero runtime cost abstractions.
This is part of the introduction to a language he's been designing since forever. I attended the International Lisp Conference in Boston around 2005 and he did a presentation there of what seems like a precursor to this.

Some of his views might be controversial, but he has plenty of Lisp implementation experience and clearly knows his stuff.

And from what I've read so far, he writes excellent documentation. I'd recommend anyone to at least skim the whole thing [0].

[0] http://users.rcn.com/david-moon/Lunar/index.html

But some are wronger than others. (I'm looking at you, Brainf*ck.)
Computation is definitely not free. I spend a lot of my time avoiding non-essential computation as much as possible.

I actually would like more control in many of the current languages.

Referring to David Moon namelessly as "the author" in comments about language design (on Hacker News of all places) does not inspire confidence. Whether or not you agree with him, Moon has been involved in language design and implementation for decades. His perspective is directly relevant to discussion that's primarily focused on large scale trends in language design. I don't necessarily agree with everything he says here, but it is definitely worth thinking about, especially where it challenges accepted dogma.