مقدمة في البرمجة الوظيفية باستخدام JavaScript
البرمجة الوظيفية (FP) هي نموذج برمجي تُعامل فيه العمليات الحسابية على أنها تطبيق للدوال الرياضية. على عكس المنهج الأمرّي، حيث نكتب سلسلة من التعليمات التي تغير حالة البرنامج، يركز الأسلوب الوظيفي على ماذا يجب حسابه، بدلاً من كيفية القيام بذلك.
لغة JavaScript، كونها لغة متعددة النماذج، مناسبة تمامًا للبرمجة الوظيفية. في بيئة Node.js، يكون هذا المنهج مفيدًا بشكل خاص لمعالجة البيانات، والعمل مع التدفقات، وبناء تطبيقات موثوقة من جانب الخادم. في هذه المقالة، سنقوم بتحليل المفاهيم الأساسية للبرمجة الوظيفية باستخدام أمثلة عملية.
المبادئ الأساسية للبرمجة الوظيفية
الدوال النقية (Pure Functions)
الدالة النقية هي دالة:
- تعيد دائمًا نفس النتيجة لنفس الوسائط (الحتمية).
- ليس لها تأثيرات جانبية (لا تعدل المتغيرات الخارجية، لا تُخرج إلى وحدة التحكم، لا تكتب في الملفات).
// Pure functionconst add = (a, b) => a + b;
// Impure function (side effect — console output)const impureAdd = (a, b) => { console.log(`Adding ${a} and ${b}`); return a + b;};
// Impure function (depends on external state)let counter = 0;const increment = () => ++counter;مزايا الدوال النقية: سهلة الاختبار، والتصحيح، والتوازي. وهي قابلة للتنبؤ ولا تعتمد على سياق التنفيذ.
الثباتية (Immutability)
الثباتية تعني أنه بمجرد إنشاء البيانات، لا يمكن تغييرها. بدلاً من تعديل كائن موجود، نقوم بإنشاء نسخة جديدة منه مع التغييرات المطلوبة.
// Mutable approach (bad)const user = { name: 'Ivan', age: 30 };user.age = 31; // Directly modifying
// Immutable approach (good)const user2 = { name: 'Maria', age: 25 };const updatedUser = { ...user2, age: 26 };// The original user2 object remains unchanged
// Working with arraysconst numbers = [1, 2, 3];const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]// numbers remains [1, 2, 3]في Node.js، تكون الثباتية مهمة بشكل خاص عند العمل مع الطلبات وحالة التطبيق لتجنب التحويرات غير المتوقعة في الكود غير المتزامن.
الدوال عالية المستوى (Higher-Order Functions) والإغلاقات (Closures)
الدوال عالية المستوى
هي دوال تأخذ دوال أخرى كوسائط أو تعيدها. تعتبر دوال المصفوفة المضمنة map و filter و reduce أمثلة كلاسيكية.
const numbers = [1, 2, 3, 4, 5];
// map — transforms each elementconst doubled = numbers.map(n => n * 2);// [2, 4, 6, 8, 10]
// filter — keeps elements based on a conditionconst evens = numbers.filter(n => n % 2 === 0);// [2, 4]
// reduce — reduces the array to a single valueconst sum = numbers.reduce((acc, n) => acc + n, 0);// 15الإغلاقات (Closures)
الإغلاق هو دالة "تتذكر" المتغيرات من النطاق الذي تم إنشاؤها فيه، حتى بعد انتهاء تنفيذ ذلك النطاق.
const createCounter = () => { let count = 0; // Private variable return () => ++count;};
const counter1 = createCounter();console.log(counter1()); // 1console.log(counter1()); // 2
const counter2 = createCounter();console.log(counter2()); // 1 (independent counter)تُستخدم الإغلاقات على نطاق واسع لإنشاء مصانع دوال (function factories) وتغليف الحالة (encapsulating state) بأسلوب وظيفي.
التحويل إلى دوال متسلسلة (Currying) وتركيب الدوال (Function Composition)
التحويل إلى دوال متسلسلة (Currying)
التحويل إلى دوال متسلسلة هو تحويل دالة تأخذ عدة وسائط إلى سلسلة من الدوال، كل منها