C++中的函数式编程。实施 f(a)(b)(c)
Functional programming in C++. Implementing f(a)(b)(c)
我一直在学习使用 C++ 进行函数式编程的基础知识。我正在尝试创建一个函数 f(a)(b)(c)
,它将 return a + b + c
。我成功实现了函数 f(a)(b)
which returns a + b。这是它的代码:
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
我只是不知道如何实现函数 f(a)(b)(c)
,正如我之前所说,应该 return a + b + c
.
您可以通过让函数 f
return 成为 functor,即实现 operator()
的对象来实现。这是一种方法:
struct sum
{
double val;
sum(double a) : val(a) {}
sum operator()(double a) { return val + a; }
operator double() const { return val; }
};
sum f(double a)
{
return a;
}
例子
int main()
{
std::cout << f(1)(2)(3)(4) << std::endl;
}
模板版本
您甚至可以编写一个模板化版本,让编译器推断类型。试试看 here.
template <class T>
struct sum
{
T val;
sum(T a) : val(a) {}
template <class T2>
auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; }
operator T() const { return val; }
};
template <class T>
sum<T> f(T a)
{
return a;
}
例子
在此示例中,T
最终将解析为 double
:
std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
只需采用您的 2 元素解决方案并通过用另一个 lambda 包装它来扩展它。
因为你想要 return 一个 lambda 得到一个 double
和 returns 一个 double
s 的附加 lambda,你需要做的就是包装你的当前 return 键入另一个函数,并在当前函数中添加一个嵌套的 lambda(一个 return 是 lambda 的 lambda):
std::function<std::function<double(double)>(double)> plus3 (double a){
return [a] (double b) {
return [a, b] (double c) {
return a + b + c;
};
};
}
如 @Ðаn 所述,您可以跳过 std::function<std::function<double(double)>(double)>
并与 auto
相处:
auto plus3 (double a){
return [a] (double b) {
return [a, b] (double c) { return a + b + c; };
};
}
您可以使用更深的嵌套 lambda 为每个元素数量扩展此结构。 4 个元素的演示:
auto plus4 (double a){
return [a] (double b) {
return [a, b] (double c) {
return [a, b, c] (double d) {
return a + b + c + d;
};
};
};
}
这里有一个稍微不同的方法,returns 从 operator()
引用 *this
,因此您没有任何副本。这是一个非常简单的仿函数实现,它递归地存储状态和左折叠:
#include <iostream>
template<typename T>
class Sum
{
T x_{};
public:
Sum& operator()(T x)
{
x_ += x;
return *this;
}
operator T() const
{
return x_;
}
};
int main()
{
Sum<int> s;
std::cout << s(1)(2)(3);
}
我能想到的最简单的方法是根据 plus2()
.
定义 plus3()
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
auto plus3(double a) {
return [a](double b){ return plus2(a + b); };
}
这将前两个参数列表组合成一个参数列表,用于调用plus2()
。这样做可以让我们以最少的重复重用我们预先存在的代码,并且可以在将来轻松扩展; plusN()
只需要 return 一个调用 plusN-1()
的 lambda,它会依次将调用传递给上一个函数,直到到达 plus2()
。可以这样使用:
int main() {
std::cout << plus2(1)(2) << ' '
<< plus3(1)(2)(3) << '\n';
}
// Output: 3 6
考虑到我们只是在线调用,我们可以轻松地将其转换为函数模板,从而无需为额外参数创建版本。
template<int N>
auto plus(double a);
template<int N>
auto plus(double a) {
return [a](double b){ return plus<N - 1>(a + b); };
}
template<>
auto plus<1>(double a) {
return a;
}
int main() {
std::cout << plus<2>(1)(2) << ' '
<< plus<3>(1)(2)(3) << ' '
<< plus<4>(1)(2)(3)(4) << ' '
<< plus<5>(1)(2)(3)(4)(5) << '\n';
}
// Output: 3 6 10 15
查看两者的实际效果 here。
如果您愿意使用库,这在 Boost's Hana 中真的很容易:
double plus4_impl(double a, double b, double c, double d) {
return a + b + c + d;
}
constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);
然后随心所欲地使用它:
int main() {
std::cout << plus4(1)(1.0)(3)(4.3f) << '\n';
std::cout << plus4(1, 1.0)(3)(4.3f) << '\n'; // you can also do up to 4 args at a time
}
这是一种状态模式单例启发方法,使用 operator()
来改变状态。
编辑: 将不必要的分配替换为初始化。
#include<iostream>
class adder{
private:
adder(double a)val(a){}
double val = 0.0;
static adder* mInstance;
public:
adder operator()(double a){
val += a;
return *this;}
static adder add(double a){
if(mInstance) delete mInstance;
mInstance = new adder(a);
return *mInstance;}
double get(){return val;}
};
adder* adder::mInstance = 0;
int main(){
adder a = adder::add(1.0)(2.0)(1.0);
std::cout<<a.get()<<std::endl;
std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl;
return 0;
}
我去玩
您想对加法进行柯里化折叠。我们可以解决这个问题,或者我们可以解决 class 个包含此问题的问题。
所以,首先,加法:
auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };
这很好地表达了加法的概念。
现在,折叠:
template<class F, class T>
struct folder_t {
F f;
T t;
folder_t( F fin, T tin ):
f(std::move(fin)),
t(std::move(tin))
{}
template<class Lhs, class Rhs>
folder_t( F fin, Lhs&&lhs, Rhs&&rhs):
f(std::move(fin)),
t(
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
)
{}
template<class U>
folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{
return {std::move(f), std::move(t), std::forward<U>(u)};
}
template<class U>
folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{
return {f, t, std::forward<U>(u)};
}
operator T()&&{
return std::move(t);
}
operator T() const&{
return t;
}
};
它需要一个种子值和一个 T,然后允许链接。
template<class F, class T>
folder_t<F, T> folder( F fin, T tin ) {
return {std::move(fin), std::move(tin)};
}
现在我们连接它们。
auto adder = folder(add, 0);
std::cout << adder(2)(3)(4) << "\n";
我们还可以使用folder
进行其他操作:
auto append = [](auto vec, auto element){
vec.push_back(std::move(element));
return vec;
};
使用:
auto appender = folder(append, std::vector<int>{});
for (int x : appender(1)(2)(3).get())
std::cout << x << "\n";
我们必须在这里调用 .get()
,因为 for(:)
循环不理解我们文件夹的 operator T()
。我们可以通过一些工作来解决这个问题,但是 .get()
更容易。
这不是 f(a)(b)(c)
,而是 curry(f)(a)(b)(c)
。我们包装 f
这样每个额外的参数要么 returns 另一个 curry
要么实际上急切地调用函数。这是 C++17,但可以通过大量额外工作在 C++11 中实现。
请注意,这是柯里化函数的解决方案——这是我从问题中得到的印象——而不是折叠二元函数的解决方案。
template <class F>
auto curry(F f) {
return [f](auto... args) -> decltype(auto) {
if constexpr(std::is_invocable<F&, decltype(args)...>{}) {
return std::invoke(f, args...);
}
else {
return curry([=](auto... new_args)
-> decltype(std::invoke(f, args..., new_args...))
{
return std::invoke(f, args..., new_args...);
});
}
};
}
为简洁起见,我跳过了转发参考。示例用法为:
int add(int a, int b, int c) { return a+b+c; }
curry(add)(1,2,2); // 5
curry(add)(1)(2)(2); // also 5
curry(add)(1, 2)(2); // still the 5th
curry(add)()()(1,2,2); // FIVE
auto f = curry(add)(1,2);
f(2); // i plead the 5th
所有这些答案看起来都非常复杂。
auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};
完全按照您的意愿行事,并且它适用于 C++11,这与此处的许多或大多数其他答案不同。
请注意,它不使用会导致性能下降的 std::function
,实际上,在许多情况下它可能会被内联。
我一直在学习使用 C++ 进行函数式编程的基础知识。我正在尝试创建一个函数 f(a)(b)(c)
,它将 return a + b + c
。我成功实现了函数 f(a)(b)
which returns a + b。这是它的代码:
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
我只是不知道如何实现函数 f(a)(b)(c)
,正如我之前所说,应该 return a + b + c
.
您可以通过让函数 f
return 成为 functor,即实现 operator()
的对象来实现。这是一种方法:
struct sum
{
double val;
sum(double a) : val(a) {}
sum operator()(double a) { return val + a; }
operator double() const { return val; }
};
sum f(double a)
{
return a;
}
例子
int main()
{
std::cout << f(1)(2)(3)(4) << std::endl;
}
模板版本
您甚至可以编写一个模板化版本,让编译器推断类型。试试看 here.
template <class T>
struct sum
{
T val;
sum(T a) : val(a) {}
template <class T2>
auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; }
operator T() const { return val; }
};
template <class T>
sum<T> f(T a)
{
return a;
}
例子
在此示例中,T
最终将解析为 double
:
std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
只需采用您的 2 元素解决方案并通过用另一个 lambda 包装它来扩展它。
因为你想要 return 一个 lambda 得到一个 double
和 returns 一个 double
s 的附加 lambda,你需要做的就是包装你的当前 return 键入另一个函数,并在当前函数中添加一个嵌套的 lambda(一个 return 是 lambda 的 lambda):
std::function<std::function<double(double)>(double)> plus3 (double a){
return [a] (double b) {
return [a, b] (double c) {
return a + b + c;
};
};
}
如 @Ðаn 所述,您可以跳过
std::function<std::function<double(double)>(double)>
并与auto
相处:auto plus3 (double a){ return [a] (double b) { return [a, b] (double c) { return a + b + c; }; }; }
您可以使用更深的嵌套 lambda 为每个元素数量扩展此结构。 4 个元素的演示:
auto plus4 (double a){ return [a] (double b) { return [a, b] (double c) { return [a, b, c] (double d) { return a + b + c + d; }; }; }; }
这里有一个稍微不同的方法,returns 从 operator()
引用 *this
,因此您没有任何副本。这是一个非常简单的仿函数实现,它递归地存储状态和左折叠:
#include <iostream>
template<typename T>
class Sum
{
T x_{};
public:
Sum& operator()(T x)
{
x_ += x;
return *this;
}
operator T() const
{
return x_;
}
};
int main()
{
Sum<int> s;
std::cout << s(1)(2)(3);
}
我能想到的最简单的方法是根据 plus2()
.
plus3()
std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
auto plus3(double a) {
return [a](double b){ return plus2(a + b); };
}
这将前两个参数列表组合成一个参数列表,用于调用plus2()
。这样做可以让我们以最少的重复重用我们预先存在的代码,并且可以在将来轻松扩展; plusN()
只需要 return 一个调用 plusN-1()
的 lambda,它会依次将调用传递给上一个函数,直到到达 plus2()
。可以这样使用:
int main() {
std::cout << plus2(1)(2) << ' '
<< plus3(1)(2)(3) << '\n';
}
// Output: 3 6
考虑到我们只是在线调用,我们可以轻松地将其转换为函数模板,从而无需为额外参数创建版本。
template<int N>
auto plus(double a);
template<int N>
auto plus(double a) {
return [a](double b){ return plus<N - 1>(a + b); };
}
template<>
auto plus<1>(double a) {
return a;
}
int main() {
std::cout << plus<2>(1)(2) << ' '
<< plus<3>(1)(2)(3) << ' '
<< plus<4>(1)(2)(3)(4) << ' '
<< plus<5>(1)(2)(3)(4)(5) << '\n';
}
// Output: 3 6 10 15
查看两者的实际效果 here。
如果您愿意使用库,这在 Boost's Hana 中真的很容易:
double plus4_impl(double a, double b, double c, double d) {
return a + b + c + d;
}
constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);
然后随心所欲地使用它:
int main() {
std::cout << plus4(1)(1.0)(3)(4.3f) << '\n';
std::cout << plus4(1, 1.0)(3)(4.3f) << '\n'; // you can also do up to 4 args at a time
}
这是一种状态模式单例启发方法,使用 operator()
来改变状态。
编辑: 将不必要的分配替换为初始化。
#include<iostream>
class adder{
private:
adder(double a)val(a){}
double val = 0.0;
static adder* mInstance;
public:
adder operator()(double a){
val += a;
return *this;}
static adder add(double a){
if(mInstance) delete mInstance;
mInstance = new adder(a);
return *mInstance;}
double get(){return val;}
};
adder* adder::mInstance = 0;
int main(){
adder a = adder::add(1.0)(2.0)(1.0);
std::cout<<a.get()<<std::endl;
std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl;
return 0;
}
我去玩
您想对加法进行柯里化折叠。我们可以解决这个问题,或者我们可以解决 class 个包含此问题的问题。
所以,首先,加法:
auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };
这很好地表达了加法的概念。
现在,折叠:
template<class F, class T>
struct folder_t {
F f;
T t;
folder_t( F fin, T tin ):
f(std::move(fin)),
t(std::move(tin))
{}
template<class Lhs, class Rhs>
folder_t( F fin, Lhs&&lhs, Rhs&&rhs):
f(std::move(fin)),
t(
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
)
{}
template<class U>
folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{
return {std::move(f), std::move(t), std::forward<U>(u)};
}
template<class U>
folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{
return {f, t, std::forward<U>(u)};
}
operator T()&&{
return std::move(t);
}
operator T() const&{
return t;
}
};
它需要一个种子值和一个 T,然后允许链接。
template<class F, class T>
folder_t<F, T> folder( F fin, T tin ) {
return {std::move(fin), std::move(tin)};
}
现在我们连接它们。
auto adder = folder(add, 0);
std::cout << adder(2)(3)(4) << "\n";
我们还可以使用folder
进行其他操作:
auto append = [](auto vec, auto element){
vec.push_back(std::move(element));
return vec;
};
使用:
auto appender = folder(append, std::vector<int>{});
for (int x : appender(1)(2)(3).get())
std::cout << x << "\n";
我们必须在这里调用 .get()
,因为 for(:)
循环不理解我们文件夹的 operator T()
。我们可以通过一些工作来解决这个问题,但是 .get()
更容易。
这不是 f(a)(b)(c)
,而是 curry(f)(a)(b)(c)
。我们包装 f
这样每个额外的参数要么 returns 另一个 curry
要么实际上急切地调用函数。这是 C++17,但可以通过大量额外工作在 C++11 中实现。
请注意,这是柯里化函数的解决方案——这是我从问题中得到的印象——而不是折叠二元函数的解决方案。
template <class F>
auto curry(F f) {
return [f](auto... args) -> decltype(auto) {
if constexpr(std::is_invocable<F&, decltype(args)...>{}) {
return std::invoke(f, args...);
}
else {
return curry([=](auto... new_args)
-> decltype(std::invoke(f, args..., new_args...))
{
return std::invoke(f, args..., new_args...);
});
}
};
}
为简洁起见,我跳过了转发参考。示例用法为:
int add(int a, int b, int c) { return a+b+c; }
curry(add)(1,2,2); // 5
curry(add)(1)(2)(2); // also 5
curry(add)(1, 2)(2); // still the 5th
curry(add)()()(1,2,2); // FIVE
auto f = curry(add)(1,2);
f(2); // i plead the 5th
所有这些答案看起来都非常复杂。
auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};
完全按照您的意愿行事,并且它适用于 C++11,这与此处的许多或大多数其他答案不同。
请注意,它不使用会导致性能下降的 std::function
,实际上,在许多情况下它可能会被内联。