18 comments

[ 2.6 ms ] story [ 35.8 ms ] thread
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.

the 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
this blog post will be a great barometer of commenters who read the post vs those who don't

  * 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.
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.
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.
(comment deleted)
> 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.

> [snip 114 lines of code]

Meanwhile, in Python:

  def flip(f):
      return lambda *args, **kwargs: f(*args[::-1], **kwargs)
(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).

There's a somewhat easier way to implement 2-argument-function flip in C++ than the blog post provides:

  #include <functional>
  constexpr auto flip(const auto& f) {
    return std::bind(f, std::placeholders::_2, std::placeholders::_1);
  }
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>{}...);
    }
  };
> Fortunately it is not just useless knowledge either: flip can be reified at will by copying the following C++17 implementation.

I hope not.

Should be using empty base optimization or [[no_unique_address]] for that implementation
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.
> 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.

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)

> `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.

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
> 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:

TXR Lisp:

  1> (mapcar [flipargs -] '(0 1 2) '(10 20 30))
  (10 19 28)
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.