البرمجة الوظيفية في TypeScript: المستوى المتقدم
\n\nالبرمجة الوظيفية (FP) هي نموذج ينظر إلى العمليات الحسابية على أنها تقييم للدوال الرياضية ويتجنب تغيير الحالة والبيانات القابلة للتغيير. TypeScript، كونه امتدادًا لـ JavaScript، يوفر نظام أنواع قوي يسمح بتطبيق العديد من مفاهيم FP على مستوى جديد من الأمان والتعبير. في هذه المقالة، سنستعرض التقنيات المتقدمة: من الثبات الصارم إلى العمل مع المونادات والفانكتورات.
\n\nالثبات والعمل مع الهياكل غير القابلة للتغيير
\n\nالثبات (عدم القابلية للتغيير) هو حجر الزاوية في FP. في TypeScript، يمكننا ضمان ذلك على مستوى الأنواع باستخدام معدّلات readonly والأنواع المساعدة.
// عدم القابلية للتغيير العميق عبر ReadonlyDeep\ntype DeepReadonly<T> = {\n readonly [P in keyof T]: DeepReadonly<T[P]>;\n};\n\ninterface User {\n name: string;\n address: { city: string; zip: number };\n}\n\nconst user: DeepReadonly<User> = {\n name: "Alice",\n address: { city: "Moscow", zip: 101000 }\n};\n\n// user.address.city = "SPb"; // خطأ! الخاصية للقراءة فقط\n\nلتحديث البيانات بأمان، استخدم الدوال النقية التي تُرجع كائنات جديدة:
\n\nfunction updateZip(user: DeepReadonly<User>, newZip: number): DeepReadonly<User> {\n return { ...user, address: { ...user.address, zip: newZip } };\n}\n\nconst updatedUser = updateZip(user, 190000);\n\nتركيب الدوال والكاري
\n\nالتركيب يسمح ببناء تحويلات معقدة من دوال بسيطة. في TypeScript، باستخدام الأدوية (Generics)، يمكننا إنشاء أدوات تركيب آمنة من حيث الأنواع.
\n\n// نوع لتركيب دالتين\ntype Compose<A, B, C> = (f: (b: B) => C, g: (a: A) => B) => (a: A) => C;\n\nconst compose: Compose<any, any, any> = (f, g) => (x) => f(g(x));\n\n// مثال: تحويل سلسلة نصية إلى رقم وزيادته بمقدار 1\nconst toNumber = (s: string): number => parseInt(s, 10);\nconst increment = (n: number): number => n + 1;\n\nconst parseAndIncrement = compose(increment, toNumber);\nconsole.log(parseAndIncrement("5")); // 6\n\nالكاري يحول دالة تأخذ عدة وسائط إلى سلسلة من الدوال التي تأخذ وسيطة واحدة. هذا يجعل التركيب أكثر مرونة:
\n\n// دالة كاري عامة مع الأنواع\nfunction curry<T1, T2, R>(fn: (a: T1, b: T2) => R): (a: T1) => (b: T2) => R {\n return (a: T1) => (b: T2) => fn(a, b);\n}\n\nconst add = (a: number, b: number): number => a + b;\nconst curriedAdd = curry(add);\nconst add5 = curriedAdd(5);\nconsole.log(add5(3)); // 8\n\nالفانكتورات والمونادات: العمل مع السياقات
\n\nالفانكتور هو هيكل بيانات يدعم عملية map. في TypeScript، يمكن أن يكون الفانكتور مصفوفة، أو Promise، أو نوعًا مخصصًا.
// فانكتور Option (Maybe) للعمل الآمن مع null/undefined\ninterface Option<T> {\n map<U>(fn: (value: T) => U): Option<U>;\n getOrElse(defaultValue: T): T;\n}\n\nclass Some<T> implements Option<T> {\n constructor(private value: T) {}\n map<U>(fn: (value: T) => U): Option<U> {\n return new Some(fn(this.value));\n }\n getOrElse(_: T): T {\n return this.value;\n }\n}\n\nclass None<T> implements Option<T> {\n map<U>(_: (value: T) => U): Option<U> {\n return new None<U>();\n }\n getOrElse(defaultValue: T): T {\n return defaultValue;\n }\n}\n\n// الاستخدام\nconst maybeNumber: Option<number> = new Some(42);\nconst result = maybeNumber.map(x => x * 2).getOrElse(0); // 84\n\nالموناد يوسع الفانكتور بطريقة flatMap (أو bind