I have the following code. Can you explain to me how it works?
template<typename Function, typename... Arguments>
auto curry(Function func, Arguments... args) {
return [=](auto... rest) {
return func(args..., rest...);
};
}
int main() {
auto add = [](auto x, auto y) {
return x + y;
};
auto add4 = curry(add, 4);
std::cout << add4(3) << '\n'; //output: 7. (Ok)
}
Currying simply means breaking up a Lambda expression so that it accepts up to one input and has up to one output. Each input and expression calculates one part of the solution, and the output from that Lambda expression is an input to the next Lambda expression in the chain.
In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it's invoked or passed as an argument to a function.
Currying simply means a transformation of a function of several arguments to a function of a single argument. This is most easily illustrated using an example: Take a function f that accepts three arguments: int f(int a,std::string b,float c) { // do something with a, b, and c return 0; }
It is possible to generate curried functions in Scheme. The function curry2 generates a curried version of a function, which accepts two parameters. The curried version takes one parameter at a time. Similarly, curry3 generates a curried version of a function that takes three parameters.
First, you have to know what currying is, or in your question, this is specifically a case of partial application (to which currying is related, but slightly different).
Basically, it means reducing a function with a certain number of parameters to create another function with some of the parameter's values fixed.
I your exampled, the original function is add(x,y)
which has two parameters x and y. You reduce the function's arity add by setting x to 4 and create the reduced function add4(y)
such as
add4(y) = add(x,y) where x = 4
Now how is this achieved in your c++ code ? With the help of variadic templates, variadic functions and lambda functions.
Lambda functions are in c++ since C++11. In essence they are anonymous function created on the fly, which can be stored in a variable. You create add
as a lambda in main()
:
auto add = [](auto x, auto y) {
return x + y;
};
One recognizes the lambda by the pattern [] (list-of-arguments) {function-body};
Note : the []
is not always empty, see "capturing variables" there, we'll get back to it later.
Now the curry function's purpose is to take one of these lambdas functions func
and a certain number of values as arguments, and define a new function by assigning the values, in order, to the first arguments of func
.
The "certain number of arguments" mechanism is permitted by the variadic template argument Arguments... args
which permits to call the template with any number of types as template parameters (as long as they are known as compile time). So in our case, the passed argument is 4, so Arguments... args
will be replaced by int
, and the instance of curry
will accept as parameters a lambda and an int
.
If we look at the code of curry
, we see that it is only a lambda function itself, (it is a variadic function, just like printf()
) whose only purpose is to concatenate the arguments whose value is already fixed when instantiating the template(args...
) and those whose value is passed as arguments to the curried function(rest...
).
The [=] sign is a special capture for lambda function which allows to use all local variables in the function body (here it allows to use args
).
So to wrap it up, here's what happening in your main function :
add
which contains a lambda
function, taking two arguments and adding them.curry(add,4)
you instantiate the curry
template.
Function
is the type of add
(a lambda taking two int
and returning an int
)4
, i.e. int
The instantiated curry
function looks like this
curry( (int,int)->(int) func, int arg){
return [=](auto... rest) {return func(arg, rest...);};
}
Note that you never see these types because of auto
and template type deduction.
You then call this instance with func
= add
and arg
= 4
You store the result of this instantiation in add4
which is now a lambda taking one parameter. You can then call add4
with 3 as argument (rest...
is 3), which will then call add(4,3)
and return 7.
Note that technically you could try to call add4
with more than one argument, since the curried function is a variadic function. The compiler will only fail when it figures out it doesn't have a place for these extra arguments when calling add
(see it here)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With