البرمجة الوظيفية في C++: مستوى متقدم
\n\nالبرمجة الوظيفية (FP) هي نموذج يعتبر العمليات الحسابية كحساب للدوال الرياضية ويتجنب الحالة القابلة للتغيير والآثار الجانبية. على الرغم من أن C++ يرتبط تقليديًا بالبرمجة الكائنية والإجرائية، إلا أن المعايير الحديثة (C++11 وما بعده) أدخلت أدوات قوية لكتابة كود وظيفي. في هذه المقالة، سنتجاوز أساسيات لامدا البسيطة وننظر في التقنيات المتقدمة: من تركيب الدوال إلى محاكاة المونادات.
\n\n1. تركيب الدوال وخطوط الأنابيب
\n\nتركيب الدوال هو عملية دمج دالتين أو أكثر لإنشاء دالة جديدة. في C++، من الملائم استخدام تعبيرات لامدا والقوالب لهذا الغرض.
\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 // معالجة خط الأنابيب (pipe) باستخدام 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 // التطبيق الجزئي عبر لامدا (أكثر اصطلاحية في 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