البرمجة الوظيفية في C++: من التعبيرات اللامبدا إلى التركيب
البرمجة الوظيفية (FP) هي نموذج حيث تُبنى الحسابات على تطبيق دوال نقية، وثبات البيانات، وغياب التأثيرات الجانبية. على الرغم من أن لغة C++ كانت في الأصل لغة متعددة النماذج، إلا أن معاييرها الحديثة (C++11/14/17/20) قدمت أدوات قوية للبرمجة الوظيفية: تعبيرات لامبدا، std::function، خوارزميات STL، الروتينات المتزامنة (coroutines)، وحتى دعم أنماط الموناد. في هذه المقالة، سنستكشف كيفية كتابة كود وظيفي في C++ دون فقدان الأداء.
1. أساسيات الأسلوب الوظيفي: لامبدا و std::function
تعبيرات لامبدا هي قلب البرمجة الوظيفية في C++. إنها تسمح لك بإنشاء دوال مجهولة "أثناء التنفيذ"، مع التقاط السياق.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; // لامبدا لتصفية الأعداد الزوجية auto is_even = [](int x) { return x % 2 == 0; }; // الاستخدام مع خوارزمية std::copy_if std::vector<int> evens; std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens), is_even); for (int n : evens) std::cout << n << " "; // 2 4 return 0;}نقاط رئيسية حول لامبدا:
- الالتقاط —
[=](بالقيمة)،[&](بالمرجع)،[this](لدوال الفئة). - النوع — كل لامبدا لها نوع فريد، لذلك يُستخدم
std::functionأو القوالب لتمريرها إلى الدوال. - قيمة الإرجاع — تُستنتج تلقائياً ما لم يُحدد خلاف ذلك.
std::function — غلاف للكائنات القابلة للاستدعاء (الدوال، لامبدا، الكائنات الدالية). إنه مناسب للتخزين والتمرير لكنه يضيف عبئاً (استدعاء افتراضي).
#include <functional>
void apply_twice(std::function<int(int)> f, int x) { std::cout << f(f(x)) << std::endl; // التركيب}
int main() { auto square = [](int x) { return x * x; }; apply_twice(square, 3); // (3^2)^2 = 81 return 0;}2. خوارزميات STL كأساس للسلاسل الوظيفية
خوارزميات STL (std::transform، std::copy_if، std::accumulate، إلخ) تتناسب تماماً مع الأسلوب الوظيفي. هي لا تعدل الحاويات مباشرة بل تأخذ مكررات وتعيد النتائج.
مثال: التصفية والتحويل (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // الطريقة التقليدية (C++11) std::vector<int> filtered; std::copy_if(data.begin(), data.end(), std::back_inserter(filtered), [](int x) { return x > 3; }); std::vector<int> result; std::transform(filtered.begin(), filtered.end(), std::back_inserter(result), [](int x) { return x * 2; }); // الطريقة الحديثة مع النطاقات (C++20) auto view = data | std::views::filter([](int x) { return x > 3; }) | std::views::transform([](int x) { return x * 2; }); for (int v : view) std::cout << v << " "; // 8 10 12 return 0;}الطي باستخدام std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // المجموع بقيمة ابتدائية 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // حاصل الضرب بقيمة ابتدائية 1 int prod = std::accumulate