مقدمة في البرمجة الوظيفية: نماذج جافا سكريبت

نشرت: 2022-03-11

البرمجة الوظيفية هي نموذج لبناء برامج الكمبيوتر باستخدام التعبيرات والوظائف دون تغيير الحالة والبيانات.

من خلال احترام هذه القيود ، تهدف البرمجة الوظيفية إلى كتابة تعليمات برمجية تكون أوضح للفهم وتكون أكثر مقاومة للأخطاء. يتم تحقيق ذلك عن طريق تجنب استخدام عبارات التحكم في التدفق ( for ، while ، break ، continue ، goto ) التي تجعل متابعة الكود أكثر صعوبة. أيضًا ، تتطلب البرمجة الوظيفية منا كتابة وظائف حتمية نقية تقل احتمالية أن تكون عربات التي تجرها الدواب.

في هذه المقالة سوف نتحدث عن القيام ببرمجة وظيفية باستخدام JavaScript. سنستكشف أيضًا طرق وميزات جافا سكريبت المختلفة التي تجعل ذلك ممكنًا. في النهاية ، سوف نستكشف المفاهيم المختلفة المرتبطة بالبرمجة الوظيفية ونرى سبب قوتها.

قبل الدخول في البرمجة الوظيفية ، يحتاج المرء إلى فهم الفرق بين الدوال الصافية وغير النقية.

وظائف نقية مقابل غير نقية

تأخذ الوظائف الصرفة بعض المدخلات وتعطي ناتجًا ثابتًا. كما أنها لا تسبب أي آثار جانبية في العالم الخارجي.

 const add = (a, b) => a + b;

هنا ، add هي وظيفة خالصة. هذا لأنه بالنسبة للقيمة الثابتة لـ a و b ، سيكون الناتج دائمًا هو نفسه.

 const SECRET = 42; const getId = (a) => SECRET * a;

getId ليست وظيفة خالصة. والسبب هو أنه يستخدم المتغير العام SECRET لحساب المخرجات. إذا تم تغيير SECRET ، getId الدالة getId قيمة مختلفة لنفس الإدخال. وبالتالي ، فهي ليست وظيفة خالصة.

 let id_count = 0; const getId = () => ++id_count;

هذه أيضًا وظيفة غير نقية ، وذلك أيضًا لسببين - (1) تستخدم متغيرًا غير محلي لحساب ناتجها ، و (2) تخلق تأثيرًا جانبيًا في العالم الخارجي عن طريق تعديل متغير في ذلك العالمية.

getId هو التوضيح النجس

قد يكون هذا مزعجًا إذا اضطررنا إلى تصحيح هذا الرمز.

ما هي القيمة الحالية لـ id_count ؟ ما هي الوظائف الأخرى التي تعدل id_count ؟ هل هناك وظائف أخرى تعتمد على id_count ؟

بسبب هذه الأسباب ، فإننا نستخدم فقط وظائف نقية في البرمجة الوظيفية.

فائدة أخرى للوظائف الصرفة هي أنه يمكن موازنتها وحفظها. ألق نظرة على الوظيفتين السابقتين. من المستحيل موازنتها أو حفظها في الذاكرة. هذا يساعد في إنشاء رمز الأداء.

مبادئ البرمجة الوظيفية

حتى الآن ، تعلمنا أن البرمجة الوظيفية تعتمد على بعض القواعد. وهم على النحو التالي.

  1. لا تغير البيانات
  2. استخدم وظائف خالصة: إخراج ثابت للمدخلات الثابتة ، وعدم وجود آثار جانبية
  3. استخدم التعبيرات والإقرارات

عندما نلبي هذه الشروط ، يمكننا القول أن الكود الخاص بنا يعمل.

البرمجة الوظيفية في JavaScript

يحتوي JavaScript بالفعل على بعض الوظائف التي تمكن البرمجة الوظيفية. مثال: String.prototype.slice ، Array.protoype.filter ، Array.prototype.join.

من ناحية أخرى ، فإن Array.prototype.forEach و Array.prototype.push هي دوال نجسة.

يمكن للمرء أن يجادل بأن Array.prototype.forEach ليس وظيفة غير نقية حسب التصميم ولكن فكر في الأمر - ليس من الممكن فعل أي شيء بها باستثناء تغيير البيانات غير المحلية أو القيام بآثار جانبية. وبالتالي ، لا بأس من وضعها في فئة الوظائف غير النقية.

أيضًا ، تحتوي JavaScript على إعلان ثابت ، وهو مثالي للبرمجة الوظيفية نظرًا لأننا لن نقوم بتغيير أي بيانات.

وظائف خالصة في JavaScript

لنلقِ نظرة على بعض الوظائف (الطرق) الخالصة التي توفرها JavaScript.

منقي

كما يوحي الاسم ، يقوم هذا بتصفية المصفوفة.

 array.filter(condition);

الشرط هنا هو دالة تحصل على كل عنصر من المصفوفة ، ويجب أن تقرر ما إذا كنت تريد الاحتفاظ بالعنصر أم لا وإرجاع القيمة المنطقية الصحيحة لذلك.

 const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]

لاحظ أن filterEven وظيفة خالصة. إذا كان غير نقي ، لكان قد جعل مرشح المرشح بأكمله غير نقي.

خريطة

map كل عنصر من عناصر المصفوفة إلى دالة وإنشاء مصفوفة جديدة بناءً على قيم الإرجاع لاستدعاءات الوظيفة.

 array.map(mapper)

mapper هي وظيفة تأخذ عنصرًا من مصفوفة كمدخل وتعيد المخرجات.

 const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]

خفض

reduce يقلل من قيمة الصفيف إلى قيمة واحدة.

 array.reduce(reducer);

reducer هي وظيفة تأخذ القيمة المتراكمة والعنصر التالي في المصفوفة وتعيد القيمة الجديدة. يطلق عليه مثل هذا لجميع القيم في المصفوفة ، واحدة تلو الأخرى.

 const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6 

تقليل التوضيح المكالمة

كونكات

يضيف concat عناصر جديدة إلى مصفوفة موجودة لإنشاء مصفوفة جديدة. إنه يختلف عن push() بمعنى أن push() يغير البيانات ، مما يجعلها غير نقية.

 [1, 2].concat([3, 4]) // [1, 2, 3, 4]

يمكنك أيضًا أن تفعل الشيء نفسه باستخدام عامل انتشار.

 [1, 2, ...[3, 4]]

كائن. تعيين

Object.assign بنسخ القيم من الكائن المتوفر إلى كائن جديد. نظرًا لأن البرمجة الوظيفية تعتمد على بيانات غير قابلة للتغيير ، فإننا نستخدمها لإنشاء كائنات جديدة بناءً على الكائنات الموجودة.

 const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2

مع ظهور ES6 ، يمكن القيام بذلك أيضًا باستخدام عامل الانتشار.

 const newObj = {...obj};

خلق الوظيفة النقية الخاصة بك

يمكننا أيضًا إنشاء وظيفتنا النقية. لنقم بعمل واحد لتكرار سلسلة n عدد من المرات.

 const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);

تقوم هذه الوظيفة بتكرار سلسلة n مرات وإرجاع سلسلة جديدة.

 duplicate('hooray!', 3) // hooray!hooray!hooray!

وظائف عالية المستوى

دالات الرتبة الأعلى هي دالات تقبل دالة كوسيطة وتعيد دالة. في كثير من الأحيان ، يتم استخدامها للإضافة إلى وظائف الوظيفة.

 const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };

في المثال أعلاه ، نقوم بإنشاء withLog ذات الترتيب الأعلى والتي تأخذ وظيفة وتقوم بإرجاع وظيفة تسجل رسالة قبل تشغيل وظيفة الالتفاف.

 const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7

يمكن استخدام withLog HOF مع وظائف أخرى أيضًا وهو يعمل بدون أي تعارضات أو كتابة تعليمات برمجية إضافية. هذا هو جمال HOF.

 const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!

يمكن للمرء أن يطلق عليه أيضًا دون تحديد وظيفة الجمع.

 withLog(hype)('Sale'); // calling hype // Sale!!!

كاري

الكاري يعني تقسيم دالة تأخذ وسيطات متعددة إلى مستوى واحد أو عدة مستويات من وظائف الترتيب الأعلى.

لنأخذ وظيفة add .

 const add = (a, b) => a + b;

عندما يتعين علينا التعامل معها ، نعيد كتابتها لتوزيع الحجج على مستويات متعددة على النحو التالي.

 const add = a => { return b => { return a + b; }; }; add(3)(4); // 7

فائدة الكاري هي الحفظ. يمكننا الآن حفظ بعض الحجج في استدعاء دالة بحيث يمكن إعادة استخدامها لاحقًا دون تكرار وإعادة الحساب.

 // assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);

هذا بالتأكيد أفضل من استخدام كلتا الحجتين في كل مكان.

 // (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());

يمكننا أيضًا إعادة تنسيق وظيفتنا الملتفة لتبدو موجزة. هذا لأن كل مستوى من استدعاء دالة الكاري عبارة عن بيان إرجاع سطر واحد. لذلك ، يمكننا استخدام وظائف الأسهم في ES6 لإعادة بنائها على النحو التالي.

 const add = a => b => a + b;

تعبير

في الرياضيات ، يُعرَّف التركيب بأنه تمرير ناتج إحدى الوظائف إلى مدخلات أخرى وذلك لإنشاء ناتج مشترك. نفس الشيء ممكن في البرمجة الوظيفية لأننا نستخدم وظائف نقية.

لإظهار مثال ، دعنا ننشئ بعض الوظائف.

الوظيفة الأولى هي النطاق ، الذي يأخذ رقم البداية a ورقم النهاية b وينشئ مصفوفة تتكون من أرقام من a إلى b .

 const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];

ثم لدينا دالة ضربها تأخذ مصفوفة وتضرب كل الأعداد الموجودة فيها.

 const multiply = arr => arr.reduce((p, a) => p * a);

سنستخدم هذه الدوال معًا لحساب المضروب.

 const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720

تشبه الوظيفة المذكورة أعلاه لحساب العامل f(x) = g(h(x)) ، مما يدل على خاصية التركيب.

كلمات ختامية

لقد مررنا بوظائف نقية وغير نقية ، وبرمجة وظيفية ، وميزات JavaScript الجديدة التي تساعد في ذلك ، وبعض المفاهيم الأساسية في البرمجة الوظيفية.

نأمل أن تثير هذه القطعة اهتمامك بالبرمجة الوظيفية وربما تحفزك على تجربتها في الكود الخاص بك. نحن على يقين من أنها ستكون تجربة تعليمية وعلامة فارقة في رحلة تطوير البرمجيات الخاصة بك.

البرمجة الوظيفية هي نموذج قوي ومدروس جيدًا لكتابة برامج الكمبيوتر. مع إدخال ES6 ، تتيح JavaScript تجربة برمجة وظيفية أفضل بكثير من أي وقت مضى.