int sum(int a, int b) { return a + b; }
std::binder1st
and std::binder2nd
, as well as the well-known auxiliary functions std::bind1st
and std::bind2nd
, which simplify the creation of objects of the above classes. However, there is one problem: these binders do not know how to work with ordinary function pointers, because they need to know the type of parameters. To solve this and other problems in STL, functors are commonly used. To be honest, I would prefer to call them functional objects, since the word "functor" since acquaintance with Haskell is associated with a completely different entity. However, in C ++ programming circles, this term was fixed precisely for designating objects that can behave like functions; besides, it is faster to write :)std::binder1st
and std::binder2nd
, which by the way work only with binary functions, require the presence of several typedef
in the functor you define: these are result_type, first_argument_type and second_argument_type. In order not to have to declare these types in your functor manually each time, you can simply inherit from std::binary_function<T0,T1,R>
, where T0 and T1 are the function argument types, and R is the return type, respectively. template <typename T> struct Sum : public std::binary_function<T, T, T> { T operator()(T const & a, T const & b) { return a + b; } }; // std::for_each(intArray.begin(), intArray.end(), std::bind1st(Sum<int>(), 42));
Sum
functor, which we defined above. I may have forgotten to say, but in STL there is already a similar functor - std :: plus <>. But we will do without him, since we wrote our own one. In general, we have a binary functor, and we need to get a partially applied unary. With lambdas, it might look like this: using namespace std; // for_each, begin end // ... Sum<int> sum; for_each(begin(intArray)), end(intArray), [sum] (int const & n) { return sum(42, n); });
sum(42, n)
, when we can write return 42 + n;
directly in the body of the lambda return 42 + n;
. The remark, of course, is correct, but we are interested in precisely the partial application of the function, if you have not forgotten. In addition, the function could be much more complicated than simply summing two numbers. sum ab = a + b someFunc intList = map (λ n → sum 42 n) intList
sum
, which takes a
and b
, and returns their sum. Next, we declared some function that takes a list as a parameter (probably a list of integers, judging by the name of the parameter) and does something with it. The map
function is an analogue of std::for_each
, i.e. it takes a function and calls it for each item in the list. As a function, we pass a lambda, which calls the function sum
, explicitly passing a fixed value as the first parameter, and the argument of the lambda naturally comes as the second parameter. None of this really matters ... Or rather, it would not matter if the same Haskell code could not be written like this: sum ab = a + b someFunc intList = map (sum 42) intList
sum
with one parameter. How does this work? Yes, very simple! :) The call (sum 42)
will give us a new function that takes one parameter and then sums it up with the number 42. In essence, this is the same partial application - we just say to the sum
function: “Here's your first parameter, remember it! But we don’t yet know the second parameter, so you’ll have to deal with it later. ” All this works due to the fact that all functions in Haskell are curried (by the way, there was such a mathematician - Haskell Curry;)). So it's time to figure out what it is.sum
, but add one more parameter to it (just in case): template <typename T1, typename T2, typename T3, typename R> R sum(T1 a, T2 b, T3 c) { return a + b + c; }
sum :: ((T1 × T2 × T3) → R)
sum :: (T1 → ((T2 × T3) → R))
sum :: (T1 → (T2 → (T3 → R)))
#include <cstddef> #include <iostream> using namespace std; template <typename R, typename T0, typename T1> function<function<R(T1)>(T0)> curry_(function<R(T0,T1)> f) { return [=] (T0 const & t0) -> function<R(T1)> { return [=] (T1 const & t1) { return f(t0, t1); }; }; } int sum(int a, int b) { return a + b; } int main() { auto curried_sum = curry_<int,int,int>(sum); cout << sum(42, 10) << endl; // => 52 cout << curried_sum(42)(10) << endl; // => 52 return EXIT_SUCCESS; }
curry_
depends on three template parameters (the types of the function arguments and the type of the return value). It takes an object of type std::function<>
as an argument. If anyone does not know, this is such a universal container that can store functors, lambdas and even function pointers (Yay! No more smutter :)). What matters to us is that it is essentially a functional object, that is, it has operator()
overloaded. Next, we simply return the unary lambda (essentially an anonymous functor), which returns another unary lambda. It is practically one-to-one translation of the definition of the term currying from Russian to C ++. std::bind1st(std::function<int(int,int)>(sum), 42)(10); // curry_<int,int,int>(sum)(42)(10);
curry_
function for a ternary function, we will fail because the compiler cannot distinguish between instances of std::function<>
. As I understand it, the reason for this is that the std::function<>
a template constructor defined, which accepts any type, but we will not go into it now, because I’m not completely sure that I understood the problem correctly. However, the fact remains. And with this you need to do something.curry_
function will curry_
left as it is, and for it we will write a template wrapper, which will take any type as a template parameter. Next, we write something like function_traits
(by the way, it’s strange that there’s no such thing in the standard, no?), In which we will memorize the types of arguments, etc. The approach of writing the so-called traits classes is universally used in the STL, so why shouldn't we do that. template <typename Func> struct function_traits {}; // template <typename R, typename T0, typename T1> struct function_traits<R(*)(T0,T1)> { typedef R result_type; typedef T0 first_argument_type; typedef T1 second_argument_type; }; // curry_ template <typename Functor> function< function< typename function_traits<Functor>::result_type(typename function_traits<Functor>::second_argument_type) >(typename function_traits<Functor>::first_argument_type) > curry(Functor f) { return curry_ < typename function_traits<Functor>::result_type , typename function_traits<Functor>::first_argument_type , typename function_traits<Functor>::second_argument_type > (f); }
cout << curry(sum)(42)(10) << endl;
curry_
for ternary functions, and even so that we do not have to start another function name for these purposes - let everything be solved by overloading functions. So far, the instinct tells me that this will be problematic. Look at least at the curry
wrapper function: it takes only one parameter (directly the function to be curried), but it should always return objects of different types depending on the arity of the function (that is, it will return a function that returns a function, or a function that returns a function, a return function, or ... mmm, perhaps enough).function_traits
, which will be initialized with different values depending on the specialization of the template. Next, we will add an additional dummy argument to our curry
wrapper function, which will only participate in resolving the function overload at the compilation stage. template <typename Func> struct function_traits {}; // template <typename R, typename T0> struct function_traits<R(*)(T0)> { typedef R result_type; typedef T0 argument_type; static const int arity = 1; }; // template <typename R, typename T0, typename T1> struct function_traits<R(*)(T0,T1)> { typedef R result_type; typedef T0 first_argument_type; typedef T1 second_argument_type; static const int arity = 2; }; // template <typename R, typename T0, typename T1, typename T2> struct function_traits<R(*)(T0,T1,T2)> { typedef R result_type; typedef T0 first_argument_type; typedef T1 second_argument_type; typedef T2 third_argument_type; static const int arity = 3; }; // template<typename Functor, int NArgs> struct count_args : std::enable_if<function_traits<Functor>::arity == NArgs> { static_assert(NArgs >= 0, "Negative number? WTF?"); }; // // , template <typename Functor> Functor curry(Functor f, typename count_args<Functor, 1>::type * = 0) { return f; } // template <typename Functor> std::function< std::function< typename function_traits<Functor>::result_type(typename function_traits<Functor>::second_argument_type) >(typename function_traits<Functor>::first_argument_type) > curry(Functor f, typename count_args<Functor, 2>::type * = 0) { return curry_ < typename function_traits<Functor>::result_type , typename function_traits<Functor>::first_argument_type , typename function_traits<Functor>::second_argument_type > (f); } // template <typename Functor> std::function< std::function< std::function< typename function_traits<Functor>::result_type(typename function_traits<Functor>::third_argument_type) >(typename function_traits<Functor>::second_argument_type) >(typename function_traits<Functor>::first_argument_type) > curry(Functor f, typename count_args<Functor, 3>::type * = 0) { return curry_ < typename function_traits<Functor>::result_type , typename function_traits<Functor>::first_argument_type , typename function_traits<Functor>::second_argument_type , typename function_traits<Functor>::third_argument_type > (f); }
std::enable_if
to “turn off” unsuitable curry
options during overload resolution. We also added the implementation of currying for unary functions, which simply returns the original function. It remains to write the implementation for the function curry_
for ternary functions. In the theoretical part, I mentioned that during the currying of the ternary function, the result would be a unary, which theoretically could return a function of two arguments, but in fact returns its curried version. With this knowledge, the implementation for the three arguments is extremely simple: <typename R, typename T0, typename T1, typename T2> function<function<function<R(T2)>(T1)>(T0)> curry_(function<R(T0,T1,T2)> f) { return [=] (T0 const & t0) -> function<function<R(T2)>(T1)> { return curry_<R,T1,T2>([=] (T1 const & t1, T2 const & t2) { return f(t0,t1,t2); }); }; }
mega
namespace and added function_traits
for functors to specializations, stuffed it into one header file and uploaded it to GitHub . It will be necessary to add README somehow :) Now we can write any nonsense using ternary functions. Yes, even like this: string foo(string s, int i, double d) { ostringstream str; str << s << ": " << i << " . = " << d << "$"; return str.str(); } int main() { cout << mega::curry(foo)("-")(2)(9.95) << endl; // => -: 2 . = 9.95$ return EXIT_SUCCESS; }
call
's with dragging different information between registers. But for the convenience you have to pay.Source: https://habr.com/ru/post/149056/
All Articles