البرمجة الوظيفية في TypeScript: من النظرية إلى التطبيق
البرمجة الوظيفية (FP) هي نموذج يتم فيه التعامل مع الحوسبة كتقييم للدوال الرياضية وتتجنب تغيير الحالة والبيانات القابلة للتغيير. TypeScript، كونها مجموعة شاملة من JavaScript، مناسبة تمامًا لتطبيق مبادئ FP بسبب نظام الكتابة الصارم ودعمها للدوال عالية المستوى. في هذه المقالة، سنقوم بتحليل المفاهيم الرئيسية للبرمجة الوظيفية في TypeScript ونوضح كيفية تطبيقها عمليًا.
1. الدوال النقية وعدم القابلية للتغيير
الدالة النقية هي دالة:
- تعيد دائمًا نفس النتيجة لنفس الوسائط (حتمية).
- ليس لها تأثيرات جانبية (لا تعدل المتغيرات الخارجية، لا تُخرج بيانات، لا تقوم بطلبات).
مثال على دالة نقية:
// دالة نقية: تجمع رقمينconst add = (a: number, b: number): number => a + b;
// تعيد دائمًا 5console.log(add(2, 3)); // 5console.log(add(2, 3)); // 5عدم القابلية للتغيير هو المبدأ الذي لا يتم فيه تغيير البيانات بعد إنشائها. بدلاً من تعديل كائن موجود، يتم إنشاء كائن جديد.
// تحديث مصفوفة غير قابل للتغييرconst numbers: number[] = [1, 2, 3];
// خطأ (تغيير):numbers.push(4);
// صحيح (غير قابل للتغيير):const newNumbers = [...numbers, 4]; // [1,2,3,4]
// تحديث كائن غير قابل للتغييرinterface User { name: string; age: number;}
const user: User = { name: 'Alice', age: 30 };const updatedUser: User = { ...user, age: 31 }; // كائن جديد2. تركيب الدوال والكاري
تركيب الدوال هو دمج دالتين أو أكثر في دالة جديدة. يتم تمرير نتيجة الدالة الأولى كوسيطة للدالة الثانية.
// تركيب دالتينconst compose = <T, U, V>(f: (x: U) => V, g: (x: T) => U) => (x: T): V => f(g(x));
const double = (x: number): number => x * 2;const increment = (x: number): number => x + 1;
const doubleThenIncrement = compose(increment, double);console.log(doubleThenIncrement(3)); // (3*2)+1 = 7الكاري هو تحويل دالة ذات وسائط متعددة إلى سلسلة من الدوال ذات وسيطة واحدة.
// دالة عاديةconst multiply = (a: number, b: number): number => a * b;
// النسخة المُكريةconst curriedMultiply = (a: number) => (b: number): number => a * b;
const multiplyBy2 = curriedMultiply(2);console.log(multiplyBy2(5)); // 10console.log(multiplyBy2(7)); // 14الكاري يبسط التطبيق الجزئي وإعادة استخدام الدوال.
3. المُرْفِقات والمونادات: Maybe و Either
المُرْفِق هو حاوية تنفذ طريقة map، مما يسمح بتطبيق دالة على القيمة داخل الحاوية. في TypeScript، المصفوفة هي مثال على المُرْفِق:
const numbers = [1, 2, 3];const doubled = numbers.map(x => x * 2); // [2, 4, 6]الموناد هو مُرْفِق يقوم بالإضافة إلى ذلك بتنفيذ طريقة flatMap (أو bind) و of (أو return). تساعد المونادات في التعامل مع التأثيرات الجانبية والقيم الفارغة والأخطاء بدون استثناءات.
لنقم بتنفيذ موناد Maybe بسيط للتعامل الآمن مع القيم الفارغة/غير المعرفة:
// موناد Maybeclass Maybe<T> { private constructor(private value: T | null) {}
static of<T>(value: T): Maybe<T> { return new Maybe(value); }
static none<T>(): Maybe<T> { return new Maybe<T>(null); }
map<U>(fn: (value: T) => U): Maybe<U> { if (this.value === null || this.value === undefined) { return Maybe.none(); } return M