Very soon after reading this I started to doubt my whole life's work in C++... std::flip? Never heard of it before, and I used to read cppreference for fun. And the name smells sus. I don't think the committee would name it "flip" if it did exist. Too short a name for something too niche. I looked at cppreference and saw that yep, this does not exist, so thought maybe this article was some kind of AI hallucination. Finally the author said it was all made up and my mind was at peace once more.
loeg 1 days ago [-]
> And the name smells sus. I don't think the committee would name it "flip" if it did exist. Too short a name for something too niche.
Yeah, this would be std::reverse_bind() or something.
jurschreuder 20 hours ago [-]
Smells sus? There is a pun-intended -sus- feature in Morwenn's popular sorting library.
The more you know :susface:
emil-lp 19 hours ago [-]
> `std::flip` finds its roots in functional programming, a domain in which it is extremely prevalent:
This is enough to determine it is written by Chatgpt.
If you're not familiar with Chatgpt, ChatGPT finds its roots in advanced machine learning research, particularly in the field of natural language processing (NLP), where it leverages deep learning models like GPT (Generative Pretrained Transformer) to understand, generate, and interact with human language based on vast amounts of data and context.
inetknght 23 hours ago [-]
Funny enough, I've known it exists. I've never really found much of a use case for it. Where I have found it "might" be useful is rare one-off APIs. If I were to write a wrapper for one API to work with an equivalent API, then maybe I might use this. But for one-off things, it's not important enough to use IMO.
On the other hand, I remember reading it and thing it was a bit-flip operation since it's in <functional> instead of <tuple>. So I was quite surprised to find that it's really much more like a tuple operation (which is template magic) than bit flipping (at runtime, or possibly also constexpr)
Valodim 19 hours ago [-]
What are you even talking about? std::flip does not exist, as asserted by both TFA and your parent comment.
(ugh, it is unnerving how certain my expectation is that the answer will start with "you're absolutely right!")
8 hours ago [-]
fooker 1 days 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.
jcranmer 1 days ago [-]
> * uniform function call syntax that is : a.foo(b) vs foo(a, b) being interchangeable.
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 1 days ago [-]
I'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 1 days ago [-]
By 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 1 days ago [-]
Unless 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
DSMan195276 1 days ago [-]
It's a difference of whether the function arguments are declared or not. If you declare a `void foo()`, and then call `foo((float)f)`, the `foo()` function is actually passed a `double` as the first argument rather than a `float`. If you instead change the declaration to `void foo(float)` then it gets passed as a `float`.
A misconception on my part, apologies. I had somehow conflated the automatic conversion from float to double that occurs in some circumstances to something that occurs in all circumstances. Or perhaps taken a quirk from a 1980s compiler (Turbo C maybe?) to be standard behaviour. Or something like that.
12 hours ago [-]
Sharlin 1 days ago [-]
I think there have been proposals to add universal call syntax since before C++98, some of them by Stroustrup himself.
jjmarr 1 days ago [-]
I don't want implicit conversion to float32_t. The type was introduced because of how annoying implicit conversions are.
xigoi 1 days 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}.
kelseyfrog 1 days ago [-]
The 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 1 days ago [-]
Well, 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.
esrauch 1 days ago [-]
The question isnt whether such cases exist, but instead whether they actually need c++26 and what the downsides are to everyone else if c++31 or whatever is still constrained by it.
kelseyfrog 1 days ago [-]
Perfect! 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 1 days ago [-]
And the standards committee will be correct.
sagarm 19 hours ago [-]
And what C++ version where these systems on? Have they even made it to C++11, let alone C++23?
wat10000 1 days ago [-]
When you say they're still in use, do you mean AS/400s or digraphs?
bongodongobob 1 days ago [-]
AS/400s
MyOutfitIsVague 1 days ago [-]
I have to ask, do you actually mean AS/400, or IBM i? Most current development for IBM i is done in PASE. I work in IBM i, and nobody I know is still writing C++ for the ILE. I imagine most of the ILE C++ is in maintenance mode and probably not using anything as modern as even C++20.
bongodongobob 23 hours ago [-]
To be fair, idk about the software side of it. I just had to consider that they actually existed and were running stuff in the environment. However, one was a VM, at an auto repair shop, believe it or not.
cyphar 12 hours ago [-]
Okay, but how many of them are going to have new software running on them built using C++31? If the toolchains are all stuck on C++11 (or more likely, C++98) then it doesn't really matter what C++31 will or won't do.
vjvjvjvjghv 1 days ago [-]
“ a@<foo>::&{b}”
This made me smile. So true
jandrese 1 days ago [-]
With every new version C++ takes one step closer to Rust.
xigoi 22 hours ago [-]
Rust 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.
So true. For me C++ syntax is unreadable, but the ideas behind it are familiar
im3w1l 1 days ago [-]
Isn'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.
codeflo 1 days ago [-]
Achieving 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.
jcranmer 1 days ago [-]
I wish C++ name lookup and overload resolution were that simple.
pjmlp 1 days ago [-]
Yep, and there are new ways with modules, and reflection, we can't have enough. :)
hackyhacky 1 days 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 23 hours ago [-]
so how does flip looks like in your favourite language?
hackyhacky 23 hours ago [-]
In 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 22 hours ago [-]
You misunderstand why the C++ version is complex.
Here's a similar version in C++ that is a one liner too.
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 22 hours ago [-]
I 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.
fooker 21 hours ago [-]
> there is no reason why a programmer should need to concern themselves
Agreed, hence my one liner version.
> ghc will optimize away Haskell's flip
Can you show me how? I am seeing 5000 lines of assembly.
The assembly is hard to understand. I recommend looking at the C-- intermediate output, using the -ddump-cmm flag, with -O2. The results in most of the functions getting inlined and flip is removed entirely.
You can see this clearly if you use conspicuous numbers. If, instead of (flip foo 2 3), you give (flip foo 2 99), you will code like this:
> 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 1 days ago [-]
It 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))
fooker 1 days ago [-]
Thinking about this a bit more, foo could be a ‘context’ object here!
data |foo> bar |> baz
This almost gets us to a point where you can express a dataflow DAG in code.
andyjohnson0 1 days ago [-]
Makes sense. Thank you!
warkdarrior 1 days ago [-]
Shouldn't that be?
data |> bar(foo) |> baz
Or maybe:
data |> bar(&foo) |> baz
fredrikholm 20 hours ago [-]
In 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.
ashvardanian 1 days ago [-]
The 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 1 days ago [-]
There was an experimental implementation for uniform function call syntax in a clang fork, so it’s clearly doable.
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 1 days ago [-]
C++ 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 1 days 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 1 days ago [-]
Preferring things to be done in a library is not mutually exclusive with adding features that require language support as well.
fooker 22 hours 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 15 hours 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.
Sharlin 15 hours ago [-]
Sum types are a fundamental feature of the type system, just like product types. C++ doesn't try to implement the concept of struct as a library abstraction, either. Or the `||` operator for that matter, even though it's always expressible with just `!` and `&&`.
fooker 13 hours ago [-]
About product types, C++ does try to implement that as a library too (std::tuple), and the result is equally disastrous :)
I don’t really disagree with you, but consider that one of the main goals of an object oriented language is to allow you to define custom types.
Sharlin 3 hours ago [-]
Sure, but my point was that user-defined nominal sum types (tagged unions such as Rust enums) are just as important as user-defined nominal product types (structs, records, classes). The concept of "one of these" is just as important as "all of these" in domain modeling. Proper support for algebraic data types is a force multiplier.
OOP chose to implement "one of" with subclasses and runtime polymorphism, but that's not really "one of these choices" but "one of an arbitrarily large space of choices" because OOP interfaces are extensible by design. You can add support for "sealed" interfaces, but at that point you basically have tagged unions with a very clumsy syntax (and bad performance depending on your object model).
C, of course, has `union`, which is what you'd expect from C: a very basic, very error-prone building block for DIY tagged unions. C++ improved (depending on who you ask) on C structs by adding privacy control, methods, and so on. Similarly C++ should improve on C unions by adding implicit tagging, soundness, exhaustivity analysis and so on, so that nobody has to write `class X { enum { a_, b_ } tag; union { A a; B b; }; }` and all the associated ceremony ever again.
monkeyelite 1 days ago [-]
How do you think sum types are implemented in functional languages?
fooker 9 hours ago [-]
Usually as a specialization of GADTs
nextos 1 days ago [-]
There 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].
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
zahlman 1 days ago [-]
I 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.
1 days ago [-]
constantcrying 1 days ago [-]
Another 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 15 hours ago [-]
“C++” and “good syntax” is a contradiction.
cocoto 1 days ago [-]
I love Haskell but when writing C++ I always avoid functional style gibberish. I feel like this style of programming only works in languages properly designed for that.
eulgro 24 hours ago [-]
When C++23 is fully supported functional style will be somewhat bearable, but even then a lot is missing. Lambdas are just too verbose to make this fun.
abalaji 1 days ago [-]
this blog post will be a great barometer of commenters who read the post vs those who don't
jeffbee 1 days ago [-]
THUITFHNGL
Fortunately almost all the functional features in the article, like range folds and negation wrappers, do exist.
mindcrime 1 days ago [-]
> THUITFHNGL
Tragic Harvest Underwrites Infrastructure That Frobs Haptic Network Generation Load?
Oh I did. I just couldn't resist the temptation to have some fun with it. :-)
scubbo 22 hours ago [-]
Can you help me out with an expansion of "TFA", when used to refer to the original post in comments? I simply don't believe that every commenter is using it to mean "The Fucking Article" in comments that are otherwise devoid of aggression or profanity.
mindcrime 11 hours ago [-]
I can't speak for anybody else, but when I see "TFA" I do indeed translate it as "The Fucking Article". But if it makes you feel better you can think of it as "The Friggin' Article" or "The Fine Article" or something else less profane.
forrestthewoods 1 days ago [-]
I skimmed the post. I have absolutely no idea what std::flip is supposed to do. All the sample code looks awful and undesirable. And that’s coming from someone who writes C++ every day. Yes I read the plot twist at the end, made me lol.
OskarS 1 days ago [-]
You write C++ every day, and you didn’t understand the is_descendant_of/is_ancestor_of example? Or how you can use it to reverse a relation like std::less?
forrestthewoods 1 days ago [-]
I don’t understand why I should care about this. It doesn’t appear to solve real problems. The examples are all dumb toys and simply writing a wrapper by hand is perfectly fine and easier to read.
You do realize it's not meant for silly situations like that, right?
loeg 1 days ago [-]
I cannot imagine a not-silly situation where it would be used.
omoikane 21 hours ago [-]
If I have a library that let me curry trailing arguments for a function, then I can see how something like std::flip() could be useful in letting me curry different arguments without costing extra lines of code. The library I had in mind is Google's RPC callbacks:
This library was written in the C++98 era. It might seem silly now because with C++11, we could use std::bind or lambda expressions instead.
forrestthewoods 1 days ago [-]
I am insufficiently clever to imagine a non-silly situation in which it is useful. If authors only present silly use cases then I am inclined to suspect their creations are only useful in silly cases. If it were useful in solving real problems they should show that as an example!
zahlman 1 days ago [-]
> Interestingly enough, most of these implementations only flip the first two parameters of whichever function they are passed, though it seems to be because most of them are based on the Haskell prelude, and handling arbitrary arity can be tricky in that language.
Probably because the use case for it with higher arity is hard to imagine. (Indeed, TFA gives only examples with binary operations.)
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
(I leave keyword arguments alone because there's no clearer semantic for "flipping" them.)
The `toolz.functoolz.flip` implementation (being restricted to binary functions) is an even simpler one-liner (https://toolz.readthedocs.io/en/latest/_modules/toolz/functo...), though accompanied by a massive docstring and admittedly simplified through a heavyweight currying decorator (which accomplishes much more than simply getting a function that does the right thing).
GrantMoyer 22 hours ago [-]
There's a somewhat easier way to implement 2-argument-function flip in C++ than the blog post provides:
The best I could get the fully general version is still pretty obtuse though:
// std doesn't have a template version of placeholders::_1, _2, etc., so we need to
// define our own.
template <int I> struct placeholder{};
template<>
template<int I>
struct std::is_placeholder<placeholder<I>> : std::integral_constant<int, I> {};
// flip must be an object so that the function type can be deduced without needing
// to explicitly specify its parameters' types.
template<typename F>
struct flip {
const F f;
// operator() deduces the argument types when the flip object is called, but really
// all we need to know is the number of arguments.
template<typename... Args>
constexpr auto operator()(Args... args) {
return bind_reversed(std::make_integer_sequence<int, sizeof...(Args)>{})(args...);
}
private:
// a helper function is needed to deduce a sequence of integers so we can bind all
// the placeholder values.
template<int... Is>
constexpr auto bind_reversed(std::integer_sequence<int, Is...>) {
return std::bind(f, placeholder<sizeof...(Is) - Is>{}...);
}
};
Jaxan 1 days ago [-]
For higher arity there is a combinatorial explosion of all the possible permutations.
But if you want to flip the 2nd and 3rd argument in Haskell it can be done by flip itself:
flip23 foo = (\x -> flip (foo x))
marvinborner 1 days ago [-]
Or just (flip .), which also allows ((flip .) .) etc. for further flips.
In Smullyan's "To Mock a Mockingbird", these combinators are described as "cardinal combinator once/twice/etc. removed", where the cardinal combinator itself defines flip.
Jaxan 1 days ago [-]
I was hoping someone would provide something more succinct ;-). Thanks!
Matheus28 1 days ago [-]
Your python code allocates an array and inverts it every function call.
The C++ code has no overhead and is equivalent to a compile time transformation.
zahlman 1 days ago [-]
Of course. But if I had to care about things on that level, and I was willing to sit through the C++ compilation process (and everything else that goes along with that), I wouldn't be using Python in the first place.
arjvik 1 days ago [-]
* Some standards such as ISO 6709 (Standard representation of geographic point location by coordinates) describe points as (latitude, longitude).
* Others such as RFC 7946 (GeoJSON) describes points as (longitude, latitude).
Using (hypothetical) std::flip to reify these APIs seems like a loaded footgun - someone is bound to accidentally use it {zero, two} times to convert between orders when it needs to be used once and wreak havoc.
wk_end 1 days ago [-]
No more of a loaded footgun than trying to do it manually, I don't think.
Geographic points should probably be represented as a labeled structure to prevent confusion and passed into functions as such. Using two separate libraries with mutually incompatible error-prone APIs as described is the real loaded footgun IMO. If you can't find better libraries, write wrappers; if you don't have time to write/maintain wrappers, pray. Anything else is just a bandaid.
usefulcat 1 days ago [-]
> someone is bound to accidentally use it {zero, two} times
That risk is inherent to the problem at hand, and has nothing to do with std::flip.
Dylan16807 1 days ago [-]
You can reduce the risk with types. If you use flip, you're not doing that, which makes flip not a very good solution here.
addaon 1 days ago [-]
Yep. The right solution here is to make latitude and longitude distinct types.
The way to turn that into an even wronger solution is to create a helper just_apply(function, lat, long) that passes the arguments to the function in the (hopefully unique) order that successfully works; so flip if needed, based on function’s type signature.
jandrewrogers 1 days ago [-]
Probably no better or worse than the alternative. That aside, the example doesn't understand the standards in question.
The governing standard for geospatial data representation is ISO 19125, which defines (longitude, latitude) order. GeoJSON naturally conforms to ISO 19125 since it is a format for processing data on computers.
ISO 6709 is essentially a print formatting standard and orthogonal to storing geospatial data on computers. That some data file formats happen to be human readable does not make ISO 6709 apply.
If you are processing geospatial data on computers the correct order is always (longitude, latitude).
kazinator 1 days ago [-]
> The LISP family of languages generally does not provide such a function by default
The Lisp family doesn't provide anything but parentheses, maybe lambda (not necessarily under that name) and a whole lot of heated arguments.
Only specific dialects of specific Lisp-family languages provide, or don't provide this and that:
flip is called flipargs because flip is the name of an operator that mutates a place with a negation of its current value: i.e (flip x) means (set x (not x)) with x evaluated once.
hackyhacky 1 days ago [-]
This seems like an obvious functional-style function that C++ should have, given the trend for functional idioms in recent versions. However, even a C++ cynic such as myself was shocked by the length and (to me) incomprehensible implementation at the end of the article. A definition of that complexity for something so simple tells me that something has gone off the rails. The equivalent function in Haskell, fwiw, can be implemented in its entirety as follows:
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
mcherm 1 days ago [-]
That DOES seem easy, but it's because the code you wrote only works for functions of two arguments.
Can you write a Haskell version that works for functions with any number of arguments?
rcxdude 1 days ago [-]
Doesn't that version work with arbitrary numbers of arguments already thanks to currying?
hackyhacky 1 days ago [-]
Sort of: the version I gave flips the first two parameters of a function, leaving the rest untouched.
The C++ std::flip from the article, however, reverses the order of all parameters. Reproducing that in Haskell would require fancy type-level programming or compile-time metaprogramming, both of which would defeat the simplicity of my initial version.
Matheus28 1 days ago [-]
Should be using empty base optimization or [[no_unique_address]] for that implementation
loeg 1 days ago [-]
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
I hope not.
1 days ago [-]
abrudz 1 days ago [-]
⍨
Rendered at 01:52:49 GMT+0000 (Coordinated Universal Time) with Vercel.
Yeah, this would be std::reverse_bind() or something.
The more you know :susface:
This is enough to determine it is written by Chatgpt.
If you're not familiar with Chatgpt, ChatGPT finds its roots in advanced machine learning research, particularly in the field of natural language processing (NLP), where it leverages deep learning models like GPT (Generative Pretrained Transformer) to understand, generate, and interact with human language based on vast amounts of data and context.
On the other hand, I remember reading it and thing it was a bit-flip operation since it's in <functional> instead of <tuple>. So I was quite surprised to find that it's really much more like a tuple operation (which is template magic) than bit flipping (at runtime, or possibly also constexpr)
(ugh, it is unnerving how certain my expectation is that the answer will start with "you're absolutely right!")
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.
Herb Sutter has proposed this: 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:)
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.
> 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
Ex: https://godbolt.org/z/TKjz3Tqqr
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}.
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.
This made me smile. So true
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.
EDIT: and if that's too exotic for you, here's the solution in Python.
Here's a similar version in C++ that is a one liner too.
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.
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.
Agreed, hence my one liner version.
> ghc will optimize away Haskell's flip
Can you show me how? I am seeing 5000 lines of assembly.
https://godbolt.org/z/KqTq5Ez5n
You can see this clearly if you use conspicuous numbers. If, instead of (flip foo 2 3), you give (flip foo 2 99), you will code like this:
97 is calculated at compile time.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?
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:
Is a more FP friendly version of:data |foo> bar |> baz
This almost gets us to a point where you can express a dataflow DAG in code.
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.
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 :)
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p26...
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.
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.
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.
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.
Except all the OOP features, which absolutely needed dedicated syntax even though they could have been a library.
I don’t really disagree with you, but consider that one of the main goals of an object oriented language is to allow you to define custom types.
OOP chose to implement "one of" with subclasses and runtime polymorphism, but that's not really "one of these choices" but "one of an arbitrarily large space of choices" because OOP interfaces are extensible by design. You can add support for "sealed" interfaces, but at that point you basically have tagged unions with a very clumsy syntax (and bad performance depending on your object model).
C, of course, has `union`, which is what you'd expect from C: a very basic, very error-prone building block for DIY tagged unions. C++ improved (depending on who you ask) on C structs by adding privacy control, methods, and so on. Similarly C++ should improve on C unions by adding implicit tagging, soundness, exhaustivity analysis and so on, so that nobody has to write `class X { enum { a_, b_ } tag; union { A a; B b; }; }` and all the associated ceremony ever again.
[1] https://github.com/graninas/cpp_functional_programming
Fortunately almost all the functional features in the article, like range folds and negation wrappers, do exist.
Tragic Harvest Underwrites Infrastructure That Frobs Haptic Network Generation Load?
https://www.urbandictionary.com/define.php?term=THUITFHNGL
https://cppreference.com/w/cpp/utility/functional/not_fn.htm...
If you don't see any value in that, you wouldn't see any value in the similar `flip` function or other combinators.
Just reverse parameter order. It seems very silly.
https://github.com/protocolbuffers/protobuf/blob/main/src/go...
This library was written in the C++98 era. It might seem silly now because with C++11, we could use std::bind or lambda expressions instead.
Probably because the use case for it with higher arity is hard to imagine. (Indeed, TFA gives only examples with binary operations.)
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.
> [snip 114 lines of code]
Meanwhile, in Python:
(I leave keyword arguments alone because there's no clearer semantic for "flipping" them.)The `toolz.functoolz.flip` implementation (being restricted to binary functions) is an even simpler one-liner (https://toolz.readthedocs.io/en/latest/_modules/toolz/functo...), though accompanied by a massive docstring and admittedly simplified through a heavyweight currying decorator (which accomplishes much more than simply getting a function that does the right thing).
But if you want to flip the 2nd and 3rd argument in Haskell it can be done by flip itself:
flip23 foo = (\x -> flip (foo x))
In Smullyan's "To Mock a Mockingbird", these combinators are described as "cardinal combinator once/twice/etc. removed", where the cardinal combinator itself defines flip.
The C++ code has no overhead and is equivalent to a compile time transformation.
Geographic points should probably be represented as a labeled structure to prevent confusion and passed into functions as such. Using two separate libraries with mutually incompatible error-prone APIs as described is the real loaded footgun IMO. If you can't find better libraries, write wrappers; if you don't have time to write/maintain wrappers, pray. Anything else is just a bandaid.
That risk is inherent to the problem at hand, and has nothing to do with std::flip.
The way to turn that into an even wronger solution is to create a helper just_apply(function, lat, long) that passes the arguments to the function in the (hopefully unique) order that successfully works; so flip if needed, based on function’s type signature.
The governing standard for geospatial data representation is ISO 19125, which defines (longitude, latitude) order. GeoJSON naturally conforms to ISO 19125 since it is a format for processing data on computers.
ISO 6709 is essentially a print formatting standard and orthogonal to storing geospatial data on computers. That some data file formats happen to be human readable does not make ISO 6709 apply.
If you are processing geospatial data on computers the correct order is always (longitude, latitude).
The Lisp family doesn't provide anything but parentheses, maybe lambda (not necessarily under that name) and a whole lot of heated arguments.
Only specific dialects of specific Lisp-family languages provide, or don't provide this and that:
TXR Lisp:
flip is called flipargs because flip is the name of an operator that mutates a place with a negation of its current value: i.e (flip x) means (set x (not x)) with x evaluated once.Can you write a Haskell version that works for functions with any number of arguments?
The C++ std::flip from the article, however, reverses the order of all parameters. Reproducing that in Haskell would require fancy type-level programming or compile-time metaprogramming, both of which would defeat the simplicity of my initial version.
I hope not.