6 ms·
C++ is surprisingly close to being a usable functional language. The two missing pieces are - * structural pattern matching * uniform function call syntax th
by fooker 1y ago
C++ is surprisingly close to being a usable functional language.
The two missing pieces are -
* structural pattern matching
* uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
With the kitchen sink approach of design I’d not be surprised if these get into the language eventually. These ideas have been proposed a few times but haven’t been seriously considered as far as I know.
- ashvardanian 1y agoThe second piece (uniform call syntax) looks convenient, though I don’t see a realistic way to integrate it into modern C++. The first (structural pattern matching) is, for me, more of a dividing line between low- and high-level languages. I tend to avoid it in my C++, just as I avoid inheritance, virtual functions, and exceptions… or `<functional>` header contents. Still, it’s always fun to stumble on corners of the STL I’d never paid attention to, even if I won’t end up using them. Thought it was worth sharing :)
- fooker 1y agoThere was an experimental implementation for uniform function call syntax in a clang fork, so it’s clearly doable.
- xigoi 1y ago> With the kitchen sink approach of design I’d not be surprised if these get into the language eventually. Based on the history of C++, they will, but with extremely bizarre syntax. Instead of a.foo(b), it will be something like a@<foo>::&{b}.
- im3w1l 1y agoIsn't structural pattern matching basically the same as creating an anonymous struct whose fields are references, with a default assignment operator? Syntax ideas should spring to mind.
- vjvjvjvjghv 1y ago“ a@<foo>::&{b}” This made me smile. So true
- pjmlp 1y agoSee new reflection syntax.
- jandrese 1y agoWith every new version C++ takes one step closer to Rust.
- xigoi 1y agoRust syntax is ugly because of C++. They didn’t want to “spend their innovation tokens”, which apparently means copying the worst existing design just because it’s popular.
- p0w3n3d 1y agoBut Java also did it with optimizing perform(a -> a.add(1)) with perform(VeryLongAndComplicatedAddingIntegerFacade::add)
- p0w3n3d 1y agoSo true. For me C++ syntax is unreadable, but the ideas behind it are familiar
- kelseyfrog 1y agoThe justification will, of course, be that the other options break digraph support on some ancient platform that has no public spec and will never sunset - likely one IBM AS/400 in a basement with a 45 year uptime. Out of "respect for existing deployments," the syntax must accommodate the relic even though no one has seen it outside of the folklore of one 1993 Usenet post. Every tool chain will begrudgingly implement it and then years later when someone proposes removing it the counter argument will be, "we can't remove it now someone is using it!" The someone, of course, is a hobbyist on the mailing list whose project depends critically in the feature.
- bongodongobob 1y agoWell, they're still in use. I've encountered 3 in the last 5 years of consulting. Just because your startup doesn't have one doesn't mean they don't exist in the manufacturing world. Are they ubiquitous? No. Are they rare? Depends on the industry.
- kelseyfrog 1y agoPerfect! The standards committee will be able to reference this reply the next time they justify eye-bleeding syntax instead of having to go mine Usenet. Thank you.
- chris_wot 1y agoAnd the standards committee will be correct.
- wat10000 1y agoWhen you say they're still in use, do you mean AS/400s or digraphs?
- bongodongobob 1y agoAS/400s
- deleted 1y ago[deleted]
- delta_p_delta_x 1y ago> * structural pattern matching https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2688r5.html#comparison-tables https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p26...
- zahlman 1y agoI don't find it surprising. My impression is that people like Herb Sutter and Alexander Stepanov actively pushed in that direction in the early days. `<functional>` was, AFAIK, part of the STL before it got incorporated into the C++ standard library.
- jcranmer 1y ago> * uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable. Herb Sutter has proposed this: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p3021r0.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p30... (twice, even, there was an older version of the paper several years ago that didn't pass). I'm resolutely opposed to such a thing because, having had to actually wade through C++'s name lookup and overload resolution rules in the past, they're a dense, confusing morass of logic that you can only stand to stare at for a half-hour or so before your brain turns to mush and you need to do something else, and anything that adds to that complexity--especially in "this makes things two things nearly equivalent"--is just a bad idea. (For an example of C++ overload resolution insanity, consider this:) // Given these overloads... void f(std::float32_t); void f(double); // Which one does this line call? Assume float/double are standard IEEE-754 types. void f((float)1);
- billforsternz 1y agoI'm going to guess f(double) because floats have always been promoted to doubles for function calls since K&R. But I'm not sure by any means. I'd be ready to get more explicit if I needed some specific behaviour.
- tredre3 1y agoBy mentioning K&R you seem to imply that C also promotes floats to doubles in function calls? But that is not the case, floats are passed as floats, as you'd expect. You can try it yourself on godbolt all the way back to GCC 3, test(float x) has always emitted movss and test(double x) will result in movsd/movlpd.
- jcranmer 1y agoUnless you're calling a variable-argument function--floats are promoted to doubles for variable-argument functions: > The arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter, if present. The integer promotions are performed on each trailing argument, and trailing arguments that have type float are promoted to double. These are called the default argument promotions. No other conversions are performed implicitly
- codeflo 1y agoAchieving uniform call syntax is easy, compilers just need to implement a new form of symbol resolution called "Kaiser lookup". It follows 14 easy to understand rules. It first looks up methods, then searches for template definitions in /tmp, then for a matching phase of any Saturn moon at time of compilation, then looks of definitions std::compat, and then in the namespaces of the types of any variables in scope anywhere on the call stack. If none of those work, it tries to interpret the call syntax as a custom float literal, and if even that fails, as a Perl 4 compatible regex. It's really intuitive if you think about it.
- constantcrying 1y agoAnother missing piece is a good syntax. C++ has, as you said, most of the capabilities already, but actually using them will quickly turn the code into symbol vomit.
- xigoi 1y ago“C++” and “good syntax” is a contradiction.
- nextos 1y agoThere are actually a few functional programming in C++ books out there. The language has changed a lot since C++98, when this would be unthinkable. Alexander Granin maintains a curated list of functional programming C++ resources [1]. [1] https://github.com/graninas/cpp_functional_programming https://github.com/graninas/cpp_functional_programming
- andyjohnson0 1y ago> uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable. Ive written a lot of c++ in the past but I'm not particularly knowledgeable about fp, so I'm wondering why this is important. Is it syntactic sugar or something more significant?
- fredrikholm 1y agoIt allows existing method-heavy code to be used in a functional style without bending the knee to more OOP inspired patterns. Think of a fluid API, but instead of chaining method calls you'd pass data to several functions "chained" together similar to how UNIX pipes work. With this type of API, you can pass one argument into the function and pipe the other such that: data |> foo(bar) |> baz Is a more FP friendly version of: return baz(foo.bar(data))
- andyjohnson0 1y agoMakes sense. Thank you!
- warkdarrior 1y agoShouldn't that be? data |> bar(foo) |> baz Or maybe: data |> bar(&foo) |> baz
- fredrikholm 1y agoIn Clojure it's: (.-> foo bar baz) So you might be right in correcting me, it's been a while since I've used another FP language in anger. With regards to taking the address of foo, pointers are generally not a (user space) concept in FP languages. The compiler/runtime usually optimizes cases like this as passing function pointers is a very large corner stone of FP. For mutation semantics you often approach it similar to how atomics works in non-FP languages. When opting into mutation you lose one of the pillars of FP which is idempotency and purity via immutability. Treating it as a special case helps scope it down to "here be dragons" areas.
- fooker 1y ago
- Sharlin 1y ago> * structural pattern matching Yes, but that has to come with proper sum types. std::variant doesn’t quite cut it. I’d also absolutely require a simpler lambda syntax. The current one is terrible for one-liner lambdas.
- fooker 1y agoC++ has a philosophy of not doing anything with the language if it can be done in library. Now the question is : what can we improve in the language so it can allow you to define a sum type better and more usable compared to std::variant? This is a surprisingly difficult question to answer, hence we haven’t had progress there.
- MattPalmer1086 1y ago> C++ has a philosophy of not doing anything with the language if it can be done in library. Then why is the language so ridiculously complicated? I had the most fun working with it back in around 2000, and even then it was quite insane. A lot has been added since then.
- aw1621107 1y agoPreferring things to be done in a library is not mutually exclusive with adding features that require language support as well.
- fooker 1y ago> Then why is the language so ridiculously complicated? Mainly as an effort to not drop compatibility with 30 years old code. You could cut the C++ standard in half if not for this.
- xigoi 1y ago> C++ has a philosophy of not doing anything with the language if it can be done in library. Except all the OOP features, which absolutely needed dedicated syntax even though they could have been a library.
- hackyhacky 1y ago> The two missing pieces are - For three decades, many people have said "C++ is nearly usable, except for just one thing..." Many of those people have gotten their way, which is why C++ is now a kitchen sink of mutually inoperable, poorly thought-out features, like someone implemented the entire index from Journal of Programming Language Design. If you don't believe me, check out the implementation of `flip` at the end of the article. Over a hundred lines of templates, variadic parameters, constexpr, r-value references, parameter forwarding, default and explicit constructors, and other things that, imho, simply should not exist in a modern programming language. So, instead, I'd say the best thing for C++, and the world, would, at this juncture to stop adding features.
- jcelerier 1y agoso how does flip looks like in your favourite language?
- hackyhacky 1y agoIn Haskell it's: flip :: (a -> b -> c) -> b -> a -> c flip f x y = f y x A little better, I'd say. Note that the first line is the type signature and is technically optional. EDIT: and if that's too exotic for you, here's the solution in Python. def flip(x): return lambda *z: x(*reversed(z))
- fooker 1y agoYou misunderstand why the C++ version is complex. Here's a similar version in C++ that is a one liner too. https://godbolt.org/z/dhsdoGcc4 https://godbolt.org/z/dhsdoGcc4 Notably, examine the simple assembly generated. In comparison, most other languages doing this will not be able to optimize away these abstractions at all.
- hackyhacky 1y agoI understand just fine why the C++ version is complex: it's because C++ is a poor language for higher-order function abstractions. Please don't make excuses for C++'s excessive inessential complexity: there is no reason why a programmer should need to concern themselves with "const r-values" and similar shenanigans in order to accomplish this simple task. The fact that the compiler is able to optimize away this code is a compiler issue, not a language issue. ghc will optimize away Haskell's flip and I didn't once have to write a compile-time index-reverser in template code.
- p0w3n3d 1y agothe best feature I love in python is: s = someobj() ref1 = s.doOne ref2 = s.doTwo // then later ref1(arg1,arg2) ref2() In Java you need to code like a crazy to have similar results. Also m = someclas.doThree // later o = someclas() o2 = someclas() for i in [o,o2]: m(i) so many patterns done easy and the right way