C++ 20's concepts are typeclasses and make object-based polymorphism obsolete - shockingly!

I am a haskelly guy who turned game developer and find myself inside the depths of C++.

So I found this quite funny and maybe someone here finds it funny/interesting, too.

Polymorphism in C++ is usually embodied by inheritance with virtual functions, i.e. I create a new type, called class in C++ of course, that implements the “virtual functions” of some other type (the “parent class”). This, arguably, is the defining chracteristic of object-oriented programming (I know that defining object-orientation is contentious and for good reasons).

In this talk, which is quite pleasant to watch and really interesting, the speaker puts a bold statement on his last slide. You don’t actually need to watch it, to participate in my amazement: with modern C++ you don’t need virtual.

Less cryptic, he is saying

The feature called concepts of the new C++ standard, which really is the same as type-classes in Haskell, makes inheritance (object-oriented polymorphism) redundant.

I am not spiteful. Usually C++ developers are well aware that their toolbox isn’t sexy. There are usually practical reasons, most often just a huge legacy codebase that is still worth maintaining somehow.

But yes, I find it funny what kind of detour C++ takes to (somehow, a little bit) abondon object-orientation. Given that modern C++ also allows modules (finally!), encapsulation, which would be the second-most important characteristic of object-oriented programming (my opinion) doesn’t imply the use of C++ classes anymore, either.


In other news: Modern C++ has support for “lazy lists” in the form of ranges and function composition (not for functions in general, though).

So does that mean, Haskell guys can just switch to C++ and excel there, too? Well of course some of them can … but don’t expect to use any of those modern C++ features. As I said, the reason why C++ is employed are quite often legacy code-bases. Modern C++ is backwards compatible (there really wouldn’t be a point, otherwise) but you can’t count on the specific C++ compiler to actually support the modern world. Less can you count on support of your coworkers and bosses, who can’t read your code anymore, once you get serious with concepts.

Also, the legacy of old-world C++ still weighs heavily. Backwards compatiblity implies awfully strange syntax and a good deal of keyword recycling in the new features. It’s like doing type artihmetic in Haskell until you discover Idris :wink:

7 Likes

Periodically I peer in on the C++ communities and marvel at some of the really cool things they’ve invented in order to avoid learning FP (joke).

In seriousness, I have seen some really nice demo code using concepts, and while it doesn’t read like C++ at all, it does seem like a language I could reach for in anger when I needed it.

5 Likes

What robs me of fun some of the days is the endless stream of null-pointer exceptions. Their conceptual origin lies partly in the design of the language but could be addressed by better library design, too. So unfortunately, libraries with interfaces that don’t drive you insane aren’t that common in the C++ ecosystem.

So in the end, C++ would be fun if it weren’t for the legacy code. And if it weren’t for the legacy code, C++ wouldn’t be.

2 Likes

This is my lament with Scala’s insistence on Java compat. The number of times I’ve seen a codebase using

Option(SomeJavaThingThatCouldBeNull).getOrElse(null)

is truly upsetting.

2 Likes

  • What extra advantages do concepts provide over plain ol’ function procedure overloading?

  • Or will that feature be (eventually) subsumed by the concept system?

Concepts allow to define a hierarchy between them, quite similar to the way type classes do (and object-based polymorphism, too)

Also, concepts are more flexible in that they allow a difference in the return type. And next to function overloading, concepts allow class overloading.

Interesting thought by the way. I have never though of function overloading as such a powerful concept. And while it’s limited, it it quite powerful now that you made me think of it.

One could argue, Haskell typeclasses are just overloaded functions where the instance is an implict parameter. (ignoring the problem with the return type)

And indeed, being more explicit about Instances would alleviate the orphaned instance problem in Haskell

1 Like

They needn’t be overloaded, the implicit parameter itself could be parameterized. E.g.:

data Monoid_ a = Monoid_ { mempty_ :: a, mappend_ :: a -> a -> a }

mappend :: Monoid_ a -> a -> a -> a
mappend = mappend_

See Haskell for all: Scrap your type classes for more.

It would also fundamentally change the nature of type classes. Also, orphan instances are not really the problem. The problem is that GHC only checks instances at usage sites, while it should be checking instances when importing.

I’d strongly recommend reading Section 4 of the Non-Reformist Reform for Haskell Modularity by Scott Kilpatrick for a much more detailed discussion of this topic.

I am interested to hear how C++ has tackled this issue. Does it allow explicit instantiation of concepts?

4 Likes

Function overloading can work with varying return types:


:-D …that was my next query, considering that Haskell overloading is DEXPTIME-complete.

1 Like

The feature called concepts of the new C++ standard, which really is the same as type-classes in Haskell, makes inheritance (object-oriented polymorphism) redundant

You might enjoy my talk Classes, Jim, but not as we know them. It was first presented at ECOOP 2009.

My thesis in the talk is that

  • OO and inheritance is one way to achieve polymorphism: writing one blob of code that works on arguments of various types.
  • Parametric polymorphism (as in Haskell or ML) is another way to achieve polymorphism.
  • As it happens though, OO languages have adopted parametric polymorphism (which they call generics) as well. The result is quite complicated.

My slightly tongue in cheek conclusion is this: once OO languages have adopted the full glory of parametric polymorphism, the scaffolding of inheritance and subtyping will be increasingly redundant. So maybe we can do without it. Which is kind of what you are saying too.

I’m not sure I truly believe this: subtyping is an appealing thing. But Haskell (which has no subtyping) gets a long long way without it.

The talk has more!

10 Likes

Dependent types in C++? Hey, why not! It has just about everything else…

Concepts in C++ have been indeed inspired by Haskell’s type classes. This is only one thing the modern C++ has adopted from Haskell, in addition to many others (ranges, monad-like expected and optional types etc).

I have the talk “Like in Haskell: Final Tagless and eDSL on concepts”. The slides are available here.

1 Like

There has been a lot of talk recently about Haskell losing market-share to languages like Rust.

And that maybe be true, but on the other hand, if you look at the trajectories of other languages: Rust, Swift, Scala, Java, Javascript, Python, C++, etc. It’s pretty easy to claim that “Haskell Won”. Almost all improvements to other languages over the last few decades are basically just them adopting features from Haskell. Lambdas won. Static typing won. Type classes won. Parametric polymorphic won. The list goes on.

All of the other languages are racing to become more like Haskell without breaking their existing niches.

1 Like

Haskell has indeed influenced a lot of languages, but it’s not the only FP language that did this. There is Lisp, there is Erlang, there is Clojure. There are many other languages with a lot of interesting features.

Parametric polymorphism was widely known before Haskell. As well as lambdas and static typing.

No, static typing is far from winning.

In general, the fact that many Haskell ideas came into other languages, doesn’t help Haskell that much.

Haskell has failed successfully.

1 Like

I feel obliged to share Stroustrup’s own words: Bjarne Stroustrup - Object Oriented Programming without Inheritance - ECOOP 2015 - YouTube

Yes, even he thinks that we might not need those subtyping related scaffolding after all.

2 Likes

I would offer that there is this ghost called object-orientation. For me, replacing any discussion of object-orientation with the discussion of well-defined concepts, is a huge step forward.

I learned a couple of new words in this thread. And those replace the confusing umbrella notion of object-orientedness. And modern C++ doesn’t rely on that anymore, either.

1 Like

People outside of the FP world also consider it to be confusing. And the degree of this confusion is much higher for Haskell with its math-inspired jargon.

My long-standing position is that OOP doesn’t deserve what haskellers usually say about it. OOP is a rich toolbox. OOP has many facets, as FP does. OOP has many good applications and is very driven by pragmatism.

I do not support the common Haskell narrative that OOP is BS.

1 Like

ah well, to me, functional programming vs. object-oriented programming is a false dichotomy.

and in order to bring about an argument, or: in order to have an opinion whether or not object-oriented programming “is BS”, we would first have to agree on what it is.

We can talk about subtype polymorphism, for example. (that’s one of the new words I learned)

1 Like

Would it be very rude if I hijacked this thread for a question of my own? There are such knowledgeable people gathered here. This might be my only chance to learn.

@rubenmoor says:

From what I have seen so far, there are two kinds of definitions:

  • Null definition. This is most often seen.
  • The definition from the book A Theory of Objects by Martín Abadi and Luca Cardelli.

The latter definition is fairly long. It proceeds by enumeration of features, such as method lookup and subclass relation, and an object oriented language they define (though they do not say it) as a family resemblance.

After thinking for a while, I figured a few possible definitions out for myself.

  1. If a language allows to syntactically localize a type with memory claiming and freeing (constructors and destructors), then it is object oriented. Historically, this seems to be the killer feature of C++ that let it overcome C — you can forget what exactly you allocated in the heap and how to free it, because C++ remembers this for you. Then, Haskell is not object oriented because it has no notion of memory claiming and freeing to begin with.

  2. An object is a constant space simulation of a discrete dynamical system, or an automaton, or a computation with state. An object oriented language is such that supports these constant space simulations. Labels for state transitions are called «methods». Haskell would be object oriented with its «state monad» type, but simulations of this type are generally not constant space. The magical ST monad and various mutable vector types seem to make Haskell truly object oriented but there are only a few types of objects and it is hard to make more.

  3. An object oriented language is such that has automatic subtyping. I have found that object oriented «classes» are essentially product types (there being methods notwithstanding). We can equip a set of typed labels defining an object with the power set lattice structure and derive inheritance from that. For example, {a: String, b: Integer} is a super-type of {a: String, b: Integer, c: Boolean}. In Haskell, we cannot automatically cast between these types, so Haskell is not object oriented by this definition. If we equip all records with the Has class thing, then maybe we can get automatic subtyping and then Haskell would be object oriented by this definition.

Does either of these definitions make sense?

My background is 95% Haskell, so I do not have a lot of practical experience with object oriented languages. For example, I have never needed to inherit something, and it is a mystery for me as to why someone would design a language to specifically support inheritance. But I understand the pain of not having extensible records — I wanted to have them, I know I could make them with type level magic, but I also know it would be a syntactic disaster. So, I am trying to understand the discourse about object oriented stuff from the experience that I do have.

I apologize again if this intrusion is not welcome.

3 Likes

“Object orientation” is an idea, nothing more. It’s worth distinguishing between “object orientation” and an “object oriented language” You can write object oriented code in regular C (for example GObject from glib, or QObject which is used internally in qemu), it’s just that an object oriented language has first class support for it.

Of course, like everyone else, I haven’t defined what “object orientation” is. I define “object orientation” as “having things called objects which have fields and use dynamic dispatch”. I’ve picked this definition because it’s the only member of the intersection of what everyone claims is “object orientation” that I can think of.

Anything around “automatic subtyping” doesn’t make sense in a dynamically typed language, and there are many object oriented languages which are dynamically typed (for example, the one for which the term was coined - Smalltalk). As for subclasses? Inheritance is optional - Golang gets by without it since “composition” is often adequate. Even classes are optional - JS gets by without them and is often called object-oriented, and I don’t think that’s a misnomer since I’ve seen a lot of GoF design patterns appear in JS code. If we look at the lisp-y object systems like CLOS or goops we also see that methods don’t have to be directly attached to objects (if they are attached directly to objects, implementing multiple dispatch is much harder).

1 Like