[SUGGESTION] Implement multiple dispatch
Hi there :)
I came here, first, to gratulate and thank you for this project, it's awesome!
So, now to my proposal. 😃
Multiple dispatch solves the expression problem.
It does so in the way, that Douglas Crockford mentioned in his famous book "JavaScript: The good parts"
If there is a feature of a language that is sometimes problematic, and if it can be replaced with another feature that is more reliable, then always use the more reliable feature.
Multiple dispatch solves the guidance problem of polymorphism.
It also solves the problem of extending and changing code written with a single inheritance in mind. I consider this to be a very nice side-effect.
The traditional methods of polymorphism (traditional in the popular sense) often have disadvantages, and C++ is famous for misunderstanding Alan Kay's initial intention of the term "object orientation."
The community adopted a 'composition over inheritance' mindset quite a while ago.
The team around Julia found unease with the typical forms of polymorphism as it had already existed in popular languages that they used before; including C++.
As they were considering certain forms of it for their language, they found that it didn't work well with the kind of data and computation scientific data was represented, and they wanted to try something new.
And on the research about that, they found that the solution to that topic has been discovered already decades ago.
As it happens. 😅
This dispatch method's cleanness and semantic elegance are easily demonstrated, and programmers often see the benefits quite quickly.
The issue with its implementations was, that it had been always a sloth in the past. It was simply never fast enough to compete with other ways of polymorphism.
And so, other means of abstracting the polymorphistic way gained more traction.
Julia set out to change that.
There are also other implementations, one in C++, and the native ones in CL and Clojure as examples.
I am today here to introduce this idea to your project and encourage you to see this little presentation of Julia's Stefan Karpinski.
Recognize that the key factor is, that code sharing only improves dramatically when used as a primary language feature.
This is of importance when thinking about the synergy of solving the guidance problem.
Julia has no way of abstracting behavior beyond functions and multiple dispatch.
So, I think it's clear that this replaces all forms of polymorphism as the default choice (and it's important to communicate this as a cultural good, as otherwise some of the benefits are lost).
Happily, existing codebases can be adjusted.
There is still a small performance penalty in the C++ library that I shared.
Depending on the number of methods implemented, it drops in performance by around 15% - 30%, when compared to virtual calls.
Considering that virtual calls take around 1-2 cycles, and that some use cases like rendering and simulations call these open multi methods frequently, we would transpile to single dispatch, as is implemented today, with one unified syntax for all dispatches.
Also, optimizing the dispatch table by type erasure and lazily dispatching (and initializing) does make some difference.
Julias' implementation of monomorphic and bimorphic dispatch is comparable in cost to virtual calls in C++, although they are closed multi methods, as said (who are inherently a bit more performant, due to the dynamic lookup.
The proposed yomm2 solution can be extended at runtime.
The lookup cost for dynamic additions to the runtime table will always add a small overhead.
I think that it is worth, implementing a system, that allows to extension of the program with a plugin system. A science-oriented language like Julia does not need so much as a more general-purpose language like Cpp.
Julia does manage to use these techniques with numerical and scientific computation in high-performance scenarios.
I have just heard of multiple dispatch the first time and watched the talk. (It is very good.)
As I understand it, multiple dispatch is a runtime functionality of Julia. Since, cppfront is just a transpiler for C++ this functionality would need to be available in C++. There are probably ways to implement this in cppfront, e.g. by adding special functionality to every class that is declared and by wrapping each function call. One problem would be that classes defined in C++ (and not in cppfront) would not be compatible with this approach.
Because of this, I do not think that adding multiple dispatch to cppfront is a valid option. Although the idea of it is very nice.
There are also other implementations, one in C++, and the native ones in CL and Clojure as examples.
I do not doubt that there is an implementation. But I presume, that my original statement still stands: In order to make it work with functions and classes outside of cppfront the appropriate declarations for the library need to be made. So it would not work out of the box.
Please correct me if I am wrong, I do not have the time for a closer look at the library.
I do not doubt that there is an implementation. But I presume, that my original statement still stands: In order to make it work with functions and classes outside of cppfront the appropriate declarations for the library need to be made.
I have fantasized about using clang to transpile to C++-with-yomm2. The way I imagined it, the transpiler would look for Stroustrup-like open-methods, then output the corresponding yomm2 constructs. There would be limitations, as yomm2 does not support repeated inheritance. Also in N2216 overriders are visible and take part in overload resolution; in yomm2 they are hidden - at least by the macros. But I think this could be solved by transpiling to the "core"API.
I'm a huge fan of Julia's multiple-dispatch and open methods. I use Julia notebooks extensively at work and it's not infrequently that you appreciate how easy it is to make two different libraries work together thanks to those design decisions.
But I think the direction of this suggestion shouldn't be "implement multiple dispatch as a part of C++2's syntax and semantics". That would take it IMO too far from what C++ is. And this language (like Swift was for Ojective-C, Typescript for Javascript, and Kotlin for Java) should be "C++ with a modern face".
I think the direction of the suggestion, that would stay within the spirit of this project, should be "let's make sure the metaprogramming tools that C++2 offers are enough to make Yomm2 look like regular code". I.e., if we took their example: https://github.com/jll63/yomm2?tab=readme-ov-file#open-methods-in-a-nutshell : Are C++2 metaprogramming annotations on the involved types and functions enough to make C++2 output the necessary register_classes, declare_method, and define_methods? (Etc. for more complex usages of Yomm2).
But I think the direction of this suggestion shouldn't be "implement multiple dispatch as a part of C++2's syntax and semantics". That would take it IMO too far from what C++ is.
Bjarne disagrees ;-)
I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. [...] Since about 1985, I have always felt some twinge of regret for not providing multi-methods. Stroustrup, 1994, The Design and Evolution of C++, 13.8.
More recently:
In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround. Stroustrup, 2020, Thriving in a Crowded and Changing World: C++ 2006–2020.
I think the direction of the suggestion, that would stay within the spirit of this project, should be "let's make sure the metaprogramming tools that C++2 offers are enough to make Yomm2 look like regular code".
Just like CLOS is written in regular Common Lisp.
I.e., if we took their example: https://github.com/jll63/yomm2?tab=readme-ov-file#open-methods-in-a-nutshell : Are C++2 metaprogramming annotations on the involved types and functions enough to make C++2 output the necessary
register_classes,declare_method, anddefine_methods? (Etc. for more complex usages of Yomm2).
In fact I have POC code that uses C++26 reflection to do away with use_classes.
But I think the direction of this suggestion shouldn't be "implement multiple dispatch as a part of C++2's syntax and semantics". That would take it IMO too far from what C++ is. And this language (like Swift was for Ojective-C, Typescript for Javascript, and Kotlin for Java) should be "C++ with a modern face".
I can kinda understand that you think multiple dispatch is too far away from modern C++ - although I disagree with - but calling Swift and Objective-C as examples, seems a bit odd.
There are not many languages who are further apart as Objective C and Swift.
The change between the two is at least as high as between C++ and Rust.
Personally, I think there is a case to be made, that pattern matching is already too far from common C++ and quite honestly, C++ has every feature under the sun, just not the optimal solution to the expression problem.
I suggest this here, as an unification for polymorphism. Multiple dispatch is the ideal way to do that, and the only downside is that its tricky to implement it performant.
And traits are also discussed in the C++ community, and multiple dispatch is simply traits that are able to be evaluated at runtime.
I think if discussing 'is this still close enough to C++' I would rather care about 'does this break fundamental C++ qualities' and I dont see that.
It fits perfectly in. ☺️
Thanks a lot
Bjarne disagrees ;-)
I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. [...] Since about 1985, I have always felt some twinge of regret for not providing multi-methods. Stroustrup, 1994, The Design and Evolution of C++, 13.8.
More recently:
In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround. Stroustrup, 2020, Thriving in a Crowded and Changing World: C++ 2006–2020.
I don't know how can someone read "I regret I didn't add this to the language from the beginning" as other than agreeing with "multimethods are too far from what C++ is". What C++ actually is. Not what one wishes C++ would have been. But to each their own.
There are not many languages who are further apart as Objective C and Swift.
I haven't touched Swift in 7 years, so it's bound to have evolved. But when it was released, and for the first years of its life, Swift was "Objective-C with modern syntax", that's it. You could trivially transpile between both languages, as the semantics remained majorly the same. This was, of course, intended as the bootstrap strategy for the language: Be transparently compatible with Obj-C libraries, and be even similar enough in some syntax aspects that you could call conventional Obj-C code from Swift in a way indistinguishable from calling any other Swift code.
This strategy is the same used by Kotlin and Typescript. And the latter is what Herb is most familiar with and has repeatedly cited as guiding decisions for this project.
As you mention pattern matching as an example of vast deviation: The way it's implemented here, I'm not aware of it being more than easy syntactic sugar layer over the if / else blocks people would already write more cumbersomely in C++. It doesn't alter the semantics of the language in any way. An example of the latter would be, e.g., doing pattern matching with unification, like Prolog.
I'm not saying these four attempts at modernizing existing languages are solely syntax sugar. Nor should be. What I'm trying to say is the compatibility with C++ semantics is more than a philosophical or aesthetic consideration "because we all like C++ but we'd like it better if it had this or that". It's technical ("can we use existing C++ libraries no problem") and strategic (otherwise the language can't bootstrap off the existing user base).
We already have UFCS (calling x.f(y) as f(x, y)) here, and multiple dispatch is a mathematical generalization of C++'s single dispatch. So there could be a way to implement it that makes it also a computational generalization (you don't pay a penalty if you don't want to use the extra bits; same semantics as C++ in that case). And someone smarter than me could come up with a design that explains how it interoperates seamlessly with C++'s std library and other C++ code in general. A design or suggestion without these two pieces is what I call too far away from C++ (in the sense of the preceding paragraph).
In fact I have POC code that uses C++26 reflection to do away with
use_classes.
That's amazing! If all of Yomm2 could be turned into C++2 metaprogramming annotations, then it's just a library that changes (by user opt-in) the semantics of a part of the language. And supporting that kind of thing is an explicit goal (like with Qt and Unreal Engine classes, etc.).
This reminded me of this other suggestion: https://github.com/hsutter/cppfront/issues/1273 is another feature I'd very much would like to see in C++2 (like multimethods), if we managed to find a good design for it. And the video linked in there explains how the creators of Swift tried to add it to the language, but couldn't if they were to remain compatible with Obj-C (so ended up creating a new language for it).
I don't know how can someone read "I regret I didn't add this to the language from the beginning" as other than agreeing with "multimethods are too far from what C++ is".
He didn't know how to implement them efficiently at the time (D&E, 13.8).
And someone smarter than me could come up with a design that explains how it interoperates seamlessly with C++'s std library and other C++ code in general. A design or suggestion without these two pieces is what I call too far away from C++ (in the sense of the preceding paragraph).
Report on language support for Multi-Methods and Open-Methods for C++, P. Pirkelbauer, Y. Solodkyy, B. Stroustrup, March 2007.
That's amazing! If all of Yomm2 could be turned into C++2 metaprogramming annotations, then it's just a library that changes (by user opt-in) the semantics of a part of the language. And supporting that kind of thing is an explicit goal (like with Qt and Unreal Engine classes, etc.).
In the meantime I discovered that C++2 and C++ can co-exist in the same file, side by side. I might give it a try later this year.
I do see value based dispatch - as currently only supported by Clojure - also as an interesting talking point.
This reminded me of this other suggestion: #1273 is another feature I'd very much would like to see in C++2 (like multimethods), if we managed to find a good design for it. And the video linked in there explains how the creators of Swift tried to add it to the language, but couldn't if they were to remain compatible with Obj-C (so ended up creating a new language for it).
restrict and similar solutions are very risky as putting the keyword in a wrong place in relation to * in the type declaration can turn an optimization attempt to built-in undefined behavior.
IIRC people behind C++ were against such idea. They were much more in favor of implementing contracts and then through contracts you could very easily assert what should never happen and the compiler would optimize it (in release builds) based on supplied assertion.