Is there a way to express the function application operator/function with Hana?

147 views Asked by At

My question

I'm referring to a function which does essentially the following (modulo const, &, perfect forwarding, or whatever is appropriate):

auto constexpr dollar = [](auto f, auto x){ return f(x); }; // Why calling it "dollar"? Keep reading...

Is such a function expressable only via Boost.Hana?

Why did I think of it?

In Haskell, such a function exists, and it's called ($) ($ in infix form), and its definition is the following (source code)

($) :: forall r a (b :: TYPE r). (a -> b) -> a -> b
f $ x =  f x

and you could write the second line simply as either of the following

(f $) = f
($) f = f

where the second form makes it apparent that ($) is essentially the same as the id (the identity function),

id :: a -> a
id x = x

just with a signature that enforces that the first argument be of function type a -> b.

Indeed, applying f to x in Haskell can be done also by writing this

f `id` x

i.e. using `id` instead of $

How is that related to Hana?

Since Hana does offer an id function, I was wondering if that (maybe together with something else) can be used to define a function application utility without manually writing the lambda at the top of this post.

The difficult part

The hard part here is that when you write f `id` x in Haskell, there's not much really a point in arguing on whether you're passing 1 or 2 arguments to id, because all functions are curried by default.

That's not true in C++. For instance I can do this:

#include <boost/hana/functional/id.hpp>
#include <iostream>
using boost::hana::id;
int main() {
    auto plus1 = [](int x){ return x + 1; };
    std::cout << id(plus1)(3) << std::endl; // prints 4
}

which looks a lot like id is curried and is being given two inputs one after the other rather than together, but that's not true. It's just that id(plus1) is returning plus1, which is fed with 3. I don't know how to get the following (which would be equivalent to plus1 `id` 3 or id plus1 3 in Haskell) work:

    std::cout << id(plus1, 3) << std::endl; // doesn't even compile obviously

The true origin of the puzzle

After reading To Mock a Mockingbird, I wondered: "How do I implement the Thrush in C++ only with Boost.Hana?" (And the Thrush is the boost::hana::flipped version of the function application operator.)


¹In reality it's not exactly the same if want to write chains of applications, as the two operators have different associativity, so f $ g $ x == f `id` (g `id` x), but this is not relevant to the question, I believe.

1

There are 1 answers

0
Enlico On

Eureka!

Here's a oneliner that seems to be a very succint solution:

constexpr auto identity = overload(id, apply);

where only Hana functions are used, namely

using boost::hana::apply;
using boost::hana::id;
using boost::hana::overload;

I haven't tested it thoroughly, but at least for some simple case it seems to work:

assert(21 == identity(21));
assert(21 == identity(times3, 7));
assert(21 == identity(times3)(7));

Having defined identity like above, the thrush is easy to implement:

constexpr auto thrush = curry<2>(flip(identity));

assert(21 == thrush(7, times3));
assert(21 == thrush(7)(times3));

(Compiler Explorer)


The identity function defined above, is very similar to this, modulo perfect forwarding and other details (which might become important when one tries to apply member functions),

constexpr auto identity = [](auto const& x, auto const&... xs) {
    if constexpr (sizeof...(xs) == 0) {
        return x;
    } else {
        return x(xs...);
    }
};