There is a common abstraction for both containers and functions. I found out about this in Haskell, and I'm trying to implement it in C ++.
Most C ++ programmers are familiar with std :: transform, roughly speaking, with a given function from type A to B, you can convert a container of type A to a container of type B.
You can convert functions in the same way, given the function foo from A to B, you can convert the function panel from Z to A to the function foo. a bar from Z to B. The implementation is simple, it's just a composition.
I would like to define the fmap function, on containers and functions, to reflect this abstraction for general programming.
The container was light (I know this is not completely common)
template <typename A, typename Func> auto fmap(Func f, vector<A> in) { vector<decltype(f(in[0]))> out_terms{}; for(auto vec : in) out_terms.push_back(f(vec)); return out_terms; }
However, a similar function for functions makes me much more nervous.
template <typename FuncT, typename Func> auto fmap(FuncT f, Func in) { return [f, in](auto x){ return f(in(x)); }; }
Despite the fact that the template will not specialize in anything other than called things, I worry that this confuses the resolution of overloading. I would like to introduce type restrictions for template parameters in order to limit their permission to function types in order to keep the namespace clean. And I was going to ask how to do it.
This abstraction is extremely general, there are appropriate fmaps for pointers to values, which, I suspect, may also conflict.
So, I think my question is: can I have two different template implementations with the same template level signature? I am pretty sure that the answer is no, but maybe something like that can be faked. And if not, what tools are available today to distinguish between overloads? Especially for function types.
It seems to me that this is a textbook for concepts, although I'm not sure.
Edit: Boost will be acceptable to use, and SFINAE in particular. I am trying to find a solution that would be familiar to most programmers, and would be as convenient and canonical as possible. I could rename fmap for layout, but then the programmer would need to know in order to pass composition to the template function that accepts fmap. This would be unsuccessful because fmap is semantically unique.
Edit 2: A trivial example of how this is used.
template <typename T> auto double_everything(T in){ auto doublef = [](auto x){return 2*x;}; return fmap(doublef, in); }
It summarizes cards over containers into cards by container. Therefore, double_everything(vector<int> {1, 2, 3})
returns a vector with its elements doubled. But double_everything([](int x){ return x + 1; })
returns a function whose output outputs are two times the outputs of the increment function. This is like doubling a sort of list. An abstraction has some nice properties, I don't just do it. In any case, renaming the fmap
function for compilation does not answer the question.
Edit 3: fmap
for pattern C
performs functions from A
to B
to functions from C<A>
to C<B>
and satisfies fmap( compose(f, g) , c ) = fmap( f, fmap( g, c ))
. This is a property of maintaining a good structure.
Functions that do this for ranges already have different names. But ranges are not the only patterns for types. Here's the fmap
for std::optional
:
template<typename T, typename Func> auto fmap(Func f, optional<T> o) -> optional<f(*o)>{ if(o) return f(*o); else {}; }
This implementation does not imply any range concepts at all, for example, fmap
for the functions presented earlier. But it satisfies the semantic requirements for fmap
.
I am trying to define fmap
for different overloads in the same way, I would define a new operator *
for a custom matrix type. Therefore, I happily define fmap
in terms of boost::transform_iterator
. Then these algorithms will work with a function common in terms of fmap
.
Here is an example of such a function:
template < template<typename, typename> class Cont, typename Fmappable, typename Alloc, typename Func> auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){ auto g = [f](Fmappable x){ return fmap(f, x); }; return fmap(g, c); }
now if we write
auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} }; auto lists_squared = map_one_deep( [](int x){return x*x;} , lists);
lists_squared
prints gives
1 4 9 16 25 36
If there were an options vector instead, the options would be square if they contained elements.
I am trying to understand how to work with higher order functions in C ++.