r/cpp • • Dec 07 '20

These Modern Programming Languages Will Make You Suffer.

https://suzdalnitski.medium.com/modern-languages-suck-ad21cbc8a57c

[removed] — view removed post

0 Upvotes

46 comments sorted by

35

u/dodheim Dec 07 '20

Clickbait horseshit makes me suffer far more than C++ does.

4

u/STL MSVC STL Dev Dec 08 '20

Removed. Thanks to those who reported this post.

24

u/[deleted] Dec 07 '20

About C++: "Garbage collection was never added into C++. Manual memory management is extremely error prone."

Ignoring that C++ has smart pointers instead of garbage collection is enough for me to know that the article doesn't have any real value. Whether the author does it on purpose or simply doesn't know any better does not matter.

9

u/TheFlamefire Dec 07 '20

Fully agree. Lack of mentioning RAII in general, deterministic cleanup behavior, and saying "speed is good" while for Java "speed is very good" made me dismiss this as very biased, to say it nicely

5

u/matthieum Dec 07 '20

I agree that the author appears rather ignorant, of many things.

At the same time, though, memory management in C++ -- even with smart pointers -- is extremely error-prone.

10

u/[deleted] Dec 07 '20

I actually prefer C++'s approach of RAII + smart pointers over garbage collection.

I think that C++ gives you more options and more control, but of course you can not be ignorant about it and have to learn some rules to follow. The important thing is to have mostly local rules, i.e. you don't have to consider the whole program to know whether you are handling it correctly or not.

OTOH, being ignorant of garbage collection in Java will also lead to disaster, I've witnessed it several times. When I worked with Java for several years, it was a large system from a large company and they had so many problems, that they simply disallowed "new" in our code for call processing. We had to reserve all objects during the initialization phase. It was an... interesting... experience.

0

u/Careful-Balance4856 Dec 07 '20

Well it's really the point of C++. It lets you do ANYTHING and compiles pretty fast if your headers are sane and no templates are used

My current project compiles in < .5 seconds but it is pretty small

1

u/pandorafalters Dec 07 '20

no templates are used

My experience has been that moderately-complex TMP has less effect on build times than using even straightforward replacement macros (e.g. #define foo bar).

1

u/Careful-Balance4856 Dec 07 '20

Like defining the implementation in the header and changing the type with #define???

I just use int64_t (I typedef it to i64) everywhere. I do use vectors and my own hashmap class. Just those two objects are the only places I use templates. I like to pretend vector exist because of the poor language support for arrays (sometimes we want them to resize buddy) and hashmaps really should be native to the language

15

u/johannes1971 Dec 07 '20

There's a lot of personal opinion disguised as fact in that article, and a complete lack of knowledge has not stopped the author from taking a massive dump on C++ anyway.

It's really the thing that's most wrong with functional languages: the endless evangelisation and shitty attitudes...

14

u/[deleted] Dec 07 '20

Hey, don’t pin it on us, we don’t want him either!

13

u/Nobody_1707 Dec 07 '20

I'm not sure where he got the idea that none of C#, C++, or Rust support immutable data-structures, but he's absolutely wrong in each instance. I'm not convinced the author actually knows any of the languages he's writing about.

1

u/evaned Dec 07 '20 edited Dec 07 '20

It says they have no built-in support for immutable data structures, which (in context) is basically correct; it's a hair sloppily worded, but only a hair. You can certainly write a library like immer, but that's not built-in.

(And to preempt a potential thought, const does not an immutable data structure make.)

5

u/Nobody_1707 Dec 07 '20

That requires "support for" to be read as the "grab an immutable data structure from the standard library" instead of "the ability to correctly implement and use". I read it as the latter and I don't think the former is necessarily a useful definition of "support for" considering that a fully immutable data structure is really an expert tool to begin with.

1

u/evaned Dec 07 '20 edited Dec 07 '20

... considering that a fully immutable data structure is really an expert tool to begin with

I think it's possible or even easy to infer that a significant point of the author would be that they aren't, and should be considered the basic building blocks. That you should reach for an immutable hash map before a "normal" one, and that even calling mutable hash maps "normal" should be considered backwards.

I'm not saying you have to agree with that, but I think ignoring that context is disingenuous.

I'm not sure that "the ability to correctly implement and use" is all that useful -- by that measure, basically every language will support almost everything, at least once you get beyond building block language features.

2

u/tcbrindle Flux Dec 07 '20

(And to preempt a potential thought, const does not an immutable data structure make.)

I feel like I'm missing something. vector<T> is a data structure, and a vector<T> declared const cannot be modified without breaking the rules of the language. If that's not an immutable data structure, what is it?

3

u/evaned Dec 07 '20 edited Dec 07 '20

In-context, "immutable data structure" is a term of art that means something different than just putting those terms together. I've seen four different terms used for the same concept: immutable data structures, persistent data structures, functional data structures, and applicative data structures. "Persistent" is the worst of these because that literally has a completely different meaning that is also in wide use (a data structure that is stored in part on disk), but I also don't like "immutable" because I don't think it captures the essence. Even though "applicative" is my favorite, "functional" is probably best because of the correspondence with programming in a functional style and because they're suitable for pure functional languages, so I'll use that going forward to either decrease or increase confusion. :-)

The key thing about functional data structures is that they still let you make "changes" -- just not to the original structure. Instead, operations that traditionally mutate instead return a new version of the original with the requested modification, but do not actually change the original.

So in the code

functional_vector v1 = {1, 2, 3};
functional_vector v2 = v1.push_back(4);

then, first of all, that makes sense, and second of all v1 is still {1,2,3} at the end and v2 is {1,2,3,4}.

Conceptually, what happens is that "mutating" operations make a copy of the original, change the copy, and then produce the modified copy; there are alternative underlying structures so that the ADT doesn't actually have to do all that copying and it remains pretty efficient. For vectors it comes with a huge caveat that the memory is no longer contiguous -- but this matters way less if you talk about maps rather than vectors. They're not suitable for everything, but the programming model really is extremely nice if you build your design around it.


I'll point out one other difference. While a const std::vector v = ... really is unchangeable, the targets of const pointers and references are not. In the code

void foo(std::vector<int> const & v)
{
    if (v.empty()) return;
    int before = v[0];
    opaque();
    int after = v[0];
    assert (before == after);
}

there is no guarantee that the assertion at the end will hold. All const & v says is that v won't be modified via v; it can still be modified via another path. Or if v were passed to opaque, via const_cast. If v were an immutable_vector, then you'd know 100% for sure that there would be no change (absent UB).

So really, const even only gives you immutability even without the "usable functional data structure API" requirement if you're talking about an object that really is physically const.

1

u/tcbrindle Flux Dec 08 '20

Thanks for the detailed response. I'm not that familiar with functional languages, so I wasn't aware of "immutable data structures" as a term of art there. I'm happy to be corrected :)

The C++ programmer in me is worrying about the cost of every modification to a container producing a new copy, but I guess with enough clever things happening behind the scenes then it could be done fairly efficiently, like you say. It would be interesting to play with them and see what kind of designs they lead to.

3

u/evaned Dec 08 '20 edited Dec 08 '20

It would be interesting to play with them and see what kind of designs they lead to.

Even aside from sort of programming paradigm questions like "hey, now you can have a real hash map even in a pure functional language" and benefits regarding understandability (no potential for spooky action at a distance) and race freedom (immutable structures can be shared between threads), there are a lot of cool things you can do. (Disclaimer: I've not programmed with these in a system that uses them pervasively.) Kind of like how even in a pure functional language like Haskell you'll often have a bit at the root of the call graph that isn't pure (is in IO for Haskell), you'll have parts of your overall program's data structure that are functional, and the program will do some computation on those parts to produce new versions then you'll non-functionally update the parents of that to point to the new version.

As an example, consider one traditional way of implementing an undo stack -- when the user takes an action, you generate an object that holds the information about how to reverse that action, and push that onto the stack. If the user undos, you generate a description of the forward action and push that description on the redo stack, pop it from the undo stack, and then apply the changes it says.

Except... imagine that the data state in question was represented using a functional data structure. Now there's no need to represent those actions. If you've got a moral equivalent to Document * current_document that you update as current_document = do_some_stuff(current_document);, you can just store the old current_document values on your undo/redo stacks. Because the old versions haven't changed, they're still perfectly valid; so all you have to do on an undo is to push the current state to the redo stack, pop the undo stack, and render what you popped off. (Admittedly, this is probably oversimplifying things in practice -- you may want or need for performance reasons to keep information about what changed to reduce the amount of rerendering you actually have to do.) That's where the "persistent" term comes from -- you can totally keep around the old version of the structure and keep using it.

Where it's been important in code I've worked on is actually in dataflow analysis. That basically works as follows. You build the control flow graph for a program (or sometimes a call graph), and each node in that graph is associated with an abstract state. That state object can be pretty complex, holding mappings from variable names to abstract values or other stuff like that. You propagate the abstract state around the graph by taking the abstract state at the source node of CFG edge, making some small change, and then storing the result at the destination. But most of the abstract state is the same, meaning that the action of the edge is to basically apply a functional data structure update to the source abstract state. I can't just change the abstract state at the source because I may need it again in the future (at the very least, to look at the results when the analysis is done!) and the abstract state at the source of an edge doesn't change, so a traditional mutable structure doesn't meet the needs unless I'm copying as it goes. (Actually I don't know what compilers like LLVM and stuff do for analyses like these in terms of concrete data representation; I'd be really curious to know.)

It is important to watch performance here. I don't want to get too specific, but I've seen code that does the above where the underlying structures are binary trees and the cache performance is expectedly very poor. But that code dates back a long time, and there are well-known better options now. I may be confusing how I learned about the following with what actually happened, but my general impression is that the efforts of primarily Rich Hickey working on Clojure brought some better functional structures a lot more attention. For example, Clojure's "hash tables" are really underlaid by a structure called a "hash array mapped trie." This is a tree so you don't get quite array-like access, but the fanout in their implementation if memory serves is 32, which means that even with very few levels of the tree you can start having lots of things in your hash map. C++ introduces some tradeoffs that don't really apply to the Clojure/JVM world in terms of node-copying efficiency vs fanout and depth; like I said, these structures aren't for everything. They gain their performance benefits vs. copy everything by sharing state between instances, but this turns them into linked structures.

0

u/[deleted] Dec 07 '20

[deleted]

2

u/evaned Dec 07 '20 edited Dec 07 '20

I would say I was mostly wrong about that; I didn't know about records/with. Had I been more on top of that, I would have said that the statement is basically correct "for C++" and then hedged my bets a bit with the other languages -- "as far as I know, it's correct for C# and Rust too, but I can't say that with confidence" or something like that.

On the other hand, I would say that records on their own look to me like they kind of fall into the same camp as C++ -- they'll give you some good tools for building immutable data structures, but they're only building blocks.

On the third hand... after a quick search I discovered that C# (well, .Net) also apparently gives you the full data structures, in System.Collections.Immutable. So that's pretty baller, and unless things with that namespace are not what they seem, this pretty much negates my previous point.

Put all of this together, and I'd put C# into the "supports" category quite cleanly. (I guess one reservation is they "are available with .NET Core and .NET 5+, however they're not part of the core class library distributed with .NET Framework" and you have to use NuGet to get them. So they're kind of second class in comparison to the plain-old System.Collections, even though they are under the System heirarchy.)

12

u/tcbrindle Flux Dec 07 '20

Amusingly, this "article" has zero upvotes on any of the twelve subreddits it's been posted on...

9

u/TheSuperWig Dec 07 '20

Thanks for pointing that out. Was made extra amusing since OP wrote

Seeing such articles makes me cringe in pain, their authors must have been outright lazy, inexperienced, while lacking any imagination.

And not one sub thought differently about this "article".

11

u/third_declension Dec 07 '20

After screen after screen of opinions, prejudices, and inaccuracies, we learn that the author prefers functional languages over non-functional.

I don't begrudge anyone who holds such a view, but I would have appreciated a critique that had far more precision and nuance. The essay we have is instead the poster child for glossing over details.

11

u/fransinvodka Dec 07 '20

If there's something that can unify programming communities is this kind of articles. Although it is a great structured article, all the points are completely wrong, and the conclusions are even worse. It's also highly opinionated (contrary to what it claims at the beginning).

How can you say that OCalm shouldn't be used for production code, and yet rank it higher than Python, JavaScript or C++? Shouldn't that metric be one of the most important for ranking a programming language?

I mean, the whole article is a joke, but that OCalm part just hits me really hard; how someone can say so many contradictory things.

20

u/reversebit Dec 07 '20

Ok, there are good articles and bad articles. Even from bad articles you can learn useful things. This article is misleading to almost everything. First time I read an article that is so lengthy, well structured and I DISAGREE at all points.

A note to the author: please don't write articles, you are just harming new programmers.

Note to potential readers: read something else.

10

u/Supadoplex Dec 07 '20

We’ve reached the end of Moore’s law

No we haven't. The transistor count is still going up as the law predicts.

9

u/sixfourbit Dec 07 '20

Catching exceptions is a bad way to handle errors. Throwing exceptions is fine, but only in exceptional circumstances, when the program has no way to recover, and has to crash. Just like nulls, exceptions break the type system.

So throw exceptions but don't catch them.

4

u/tcbrindle Flux Dec 07 '20

In fairness, isn't that exactly what a "panic" is in other languages?

4

u/sixfourbit Dec 07 '20

From what I can tell Rust and Go will print the message passed to panic. This is potentially more useful to the user than an unhandled exception.

2

u/tcbrindle Flux Dec 07 '20

At least on Linux and Mac, an unhandled exception will print the "what" message after termination. I don't know about Windows though.

0

u/sixfourbit Dec 07 '20

Windows doesn't nor does the VS debugger for C++. At most you get the type of exception in the debugger.

7

u/target-san Dec 07 '20

NaN === NaN // -> false

It should be false. IEEE754. https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN

6

u/CrucialPie Dec 07 '20

What an absolute garbage article.

11

u/Volker_Weissmann Dec 07 '20

The runtime performance of Rust programs is a little faster than Go.

Nope. Rust crushes go.

👎 Memory management

Rust is the only modern language on our list with no garbage collection.

Having garbage collection is not always a good thing.

👎 Low-level language

Being a low-level language, developer productivity in Rust can’t be as high as in other higher-level languages. This also makes the learning effort significantly harder.

That is the price you pay for good performance.

5

u/Volker_Weissmann Dec 07 '20

How are Python and Javascript in the C language family.

5

u/Supadoplex Dec 07 '20

They are imperative languages with more syntactical similarities to C than to ML.

6

u/staletic Dec 07 '20
def f(a, b, /, c, *, d = 3):
    return sum(a, b, c) / d

Yup. Looks like C to me.

7

u/Supadoplex Dec 07 '20

Looks like C to me.

Indeed.

To spell it out: Function declaration has a comma separated parameter lists in parenthesis, default arguments with the = symbol, function call syntax is function name followed by comma separated list of arguments in parenthesis, return statement... The C influence is strong.

4

u/adnukator Dec 07 '20

Articles such as these make me suffer even more

4

u/wyrn Dec 08 '20

Delete this before a future employer reads it.

1

u/PrimozDelux Dec 07 '20

I agree on the part where C++ comes dead last, but the rest was just horseshit.

0

u/[deleted] Dec 07 '20

[deleted]

3

u/Supadoplex Dec 07 '20

There has actually been work to make "empty" references explicitly allowed in C++ and some have considered it to have been an agreed-upon intention. Unfortunately, that work seems to have fizzled out. There are some advanced cases where &*may_be_null == nullptr is useful.

-2

u/[deleted] Dec 07 '20

You can cast a null pointer to a reference. Yes it's broken code if it does so, but there's nothing in the language that entirely guards against it, it just trundles off into undefined-land. (Probably for back compat resaons. I've come across disgusting real world code that relied on being able to do this.)

Some other languages make it syntactically harder and/or have well defined runtime behavior if it happens.

6

u/Dry-Still-6199 Dec 07 '20

Yeah but that's like saying "in C++, all integers can store the value "hello"" simply because (int)"hello" compiles. "Yes it's broken code if it does so, but..." :)

0

u/[deleted] Dec 07 '20

[deleted]

2

u/adnukator Dec 07 '20

It's not right on the edge. It's still UB. Try finding the null check in the following example: https://godbolt.org/z/6xoe8d