I want a Functor concept in C++20.
A functor is a higher-kinded type that can be mapped over. A simple example is std::optional; with a function from type A to type B and a std::optional<A>, you can easily create a std::optional<B> by applying the function to the value if it exists and returning an empty optional otherwise. This operation is called fmap in Haskell.
template<typename A, typename B>
std::optional<B> fmap(std::function<B(A)> f, std::optional<A> fa) {
if (!fa) {
return std::optional<B>{};
}
return std::optional<B>(f(*fa));
}
A concept for all functors is simple enough to write. I've come up with this (using GCC—you'll have to remove bool to get this working in Clang, I think):
template<template<typename> typename F, typename A, typename B>
concept bool Functor = requires(std::function<B(A)> f, F<A> fa) {
{ fmap(f, fa) } -> F<B>;
};
And a simple additional function to make sure this works:
template<typename A, typename B>
std::function<B(A)> constant(B b) {
return [b](A _) { return b; };
}
template<template<typename> typename F, typename A, typename B>
F<B> replace(B b, F<A> fa) requires Functor<F,A,B> {
return fmap(constant<A,B>(b), fa);
}
It works. But it's not pretty. What I want is for the signature of replace to read like so:
template<Functor F, typename A, typename B>
F<B> replace(B b, F<A> fa);
No need for a requires-clause here. Much nicer, don't you agree? To get this to work, however, I'd have to reduce the template on my concept to a single argument. Something like this:
template<template<typename> typename F>
concept bool Functor = requires(function<B(A)> f, F<A> fa) { // Uh-oh
{ fmap(f, fa) } -> F<B>;
};
Problem is, I've not declared types A and B. As far as I can tell, there's nowhere I can declare them before I have to use them. Can what I want be done, and can it be done simply and elegantly?
One possible solution that comes to my mind is to make the requires-expression in the concept a template (or at least a template-like thing). I'd then have something like this:
template<template<typename> typename F>
concept bool Functor = requires<typename A, typename B>(function<B(A)> f, F<A> fa) {
{ fmap(f, fa) } -> F<B>;
};
Unfortunately, this is not valid by the C++20 standard and will not compile with g++-8. Could something like this be viable? Could it make it into the standard?
C++ doesn't have parametric polymorphism like this - you can't do things like "for any type" in the way you want to, and in the way you can in Haskell. I think that's fundamentally impossible in a world where overloading exists.
What you have is the following (I went ahead and removed the erroneous
bool, which isn't part of C++20 concepts, and fixed-> Type, which was also removed):What you want to say is for any types
aandb, given ana -> b, you can call this function. We can't do that. But we can just pick arbitrary types ourselves. One way of doing that is to pick secret types that the functor implementation just wouldn't know about:That's probably your best bet. You could even add multiple
a/bpairs to make this more likely to be correct.Regardless, this:
Isn't going to happen anyway, since we don't have that kind of terse syntax for constrained template template parameters. You'd have to write it this way:
As a side note, this is a bad implementation of
fmapforoptional:Taking a
std::function<Sig>means this will only work if you pass in specifically astd::function. Not for lambdas or function pointers or other function objects (like thesecret::FI used earlier). And even if it did work, you wouldn't want to do this anyway, since it's needless overhead.You want:
I have a whole post about this exact problem, Declarations Using Concepts.