C++ 函数式编程:高级教程
\n\n函数式编程(FP)是一种将计算视为数学函数求值,并避免可变状态和副作用的编程范式。尽管 C++ 传统上与面向对象和过程式编程相关联,但现代标准(C++11 及更高版本)引入了强大的工具来编写函数式代码。在本文中,我们将超越基本的 lambda,探讨高级技术:从函数组合到单子模拟。
\n\n1. 函数组合与管道
\n\n函数组合是将两个或多个函数合并以创建一个新函数的过程。在 C++ 中,使用 lambda 表达式和模板可以方便地实现这一点。
\n\n#include <iostream>\n#include <functional>\n#include <vector>\n#include <algorithm>\n\n// 两个函数组合的模板\ntemplate<typename F, typename G>\nauto compose(F f, G g) {\n return [f, g](auto x) {\n return f(g(x));\n };\n}\n\n// 使用示例\nint main() {\n auto addTwo = [](int x) { return x + 2; };\n auto multiplyByThree = [](int x) { return x * 3; };\n\n // 组合:先 multiplyByThree,再 addTwo\n auto composed = compose(addTwo, multiplyByThree);\n\n std::cout << composed(5) << std::endl; // (5*3) + 2 = 17\n\n // 使用 std::ranges 进行管道处理(C++20)\n std::vector<int> numbers = {1, 2, 3, 4, 5};\n auto result = numbers\n | std::views::filter([](int n) { return n % 2 == 0; })\n | std::views::transform([](int n) { return n * n; });\n\n for (int n : result) {\n std::cout << n << " "; // 4 16\n }\n return 0;\n}\n\n\n使用 std::views(C++20)的管道允许构建转换链而无需创建中间容器,这是一种经典的函数式方法。
2. 柯里化(Currying)与部分应用
\n\n柯里化是将一个接受多个参数的函数转换为一系列函数,每个函数只接受一个参数。部分应用则固定部分参数。
\n\n#include <iostream>\n#include <functional>\n\n// 柯里化的加法函数\nauto curriedAdd = [](int a) {\n return [a](int b) {\n return [a, b](int c) {\n return a + b + c;\n };\n };\n};\n\n// 使用 std::bind 进行部分应用\nint multiply(int a, int b, int c) {\n return a * b * c;\n}\n\nint main() {\n // 使用柯里化\n auto add5 = curriedAdd(5);\n auto add5And3 = add5(3);\n std::cout << add5And3(2) << std::endl; // 10\n\n // 通过 std::bind 进行部分应用\n using namespace std::placeholders;\n auto multiplyBy2 = std::bind(multiply, _1, _2, 2);\n std::cout << multiplyBy2(3, 4) << std::endl; // 3*4*2 = 24\n\n // 通过 lambda 进行部分应用(在 C++14+ 中更地道)\n auto multiplyBy3 = [](int a, int b) { return multiply(a, b, 3); };\n std::cout << multiplyBy3(5, 6) << std::endl; // 5*6*3 = 90\n\n return 0;\n}\n\n\n柯里化在与高阶函数(如 std::transform 或 std::accumulate)一起使用时特别有用,允许“即时”创建函数的特化版本。
3. 函子、应用函子与单子模拟
\n\n在 Haskell 中,单子是一种处理副作用的强大模式。在 C++ 中,我们可以使用类模板来模拟它们。考虑一个简单的 Maybe 单子(类似于 std::optional),但带有单子操作。
#include <iostream>\n#include <functional>\n#include <optional>\n\n// Maybe 单子(std::optional 的包装器)\ntemplate<typename T>\nclass Maybe {\n s