البرمجة الوظيفية: دليل شامل للمبتدئين في JavaScript

دقائق القراءة: 17

مقدمة إلى عالم البرمجة الوظيفية

تُعد لغة JavaScript لغة متعددة النماذج (multi-paradigm)، مما يعني إمكانية كتابة الكود الخاص بها باتباع أنماط برمجية مختلفة. يُشير النموذج البرمجي (programming paradigm) بشكل أساسي إلى مجموعة من القواعد والمبادئ التي يتبعها المبرمج عند كتابة الكود. وُجدت هذه النماذج لحل المشكلات التي يواجهها المبرمجون، ولكل منها قواعده وإرشاداته الخاصة التي تساعد على كتابة كود أفضل وأكثر كفاءة. كل نموذج يساهم في حل مشكلة معينة، لذا فإن فهم هذه النماذج يُعد أمرًا بالغ الأهمية.

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

البرمجة التصريحية مقابل البرمجة الأمرية: فهم الفروقات الجوهرية

من الأمثلة على النماذج البرمجية التي ذكرتها سابقًا البرمجة كائنية التوجه (Object-Oriented Programming)، والبرمجة الوظيفية (Functional Programming). فما هي البرمجة الوظيفية تحديدًا؟

البرمجة الوظيفية هي نموذج فرعي من نموذج البرمجة التصريحية (Declarative Programming Paradigm)، ولها قواعدها الخاصة التي يجب اتباعها عند كتابة الكود.

ما هو نموذج البرمجة التصريحية؟

عند البرمجة بلغة تتبع النموذج التصريحي، فإنك تكتب كودًا يحدد ماذا تريد أن تفعله، دون أن تحدد كيفية القيام بذلك. من الأمثلة البسيطة جدًا على ذلك لغات مثل SQL أو HTML:

SELECT * FROM customers
<div></div>

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

ما هو نموذج البرمجة الأمرية؟

إذا كنت تبرمج بلغة تتبع النموذج الأمري (Imperative/Procedural Paradigm)، فإنك تكتب كودًا يخبر الحاسوب كيفية القيام بشيء ما. على سبيل المثال، إذا قمت بشيء مثل التالي:

for (let i = 0; i < arr.length; i++) {
  increment += arr[i];
}

أنت تخبر الحاسوب بالضبط ماذا يفعل. قم بالتكرار عبر المصفوفة المسماة arr، ثم قم بـ increment (زيادة) كل عنصر من عناصر المصفوفة. أنت تحدد الخطوات التفصيلية التي يجب اتباعها.

البرمجة التصريحية مقابل البرمجة الأمرية في JavaScript

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

إذا كان هذا يساعدك على التذكر، فإن مثالًا على أمر تصريحي سيكون أن تطلب من الحاسوب أن يصنع لك كوبًا من الشاي (لا يهمني كيف تفعل ذلك، فقط أحضر لي بعض الشاي). بينما بشكل أمري، سيتعين عليك أن تقول:

  • اذهب إلى المطبخ.
  • إذا كان هناك غلاية في الغرفة، وبها ماء كافٍ لكوب من الشاي، قم بتشغيل الغلاية.
  • إذا كان هناك غلاية في الغرفة، ولم يكن بها ماء كافٍ لكوب من الشاي، املأ الغلاية بماء كافٍ لكوب من الشاي، ثم قم بتشغيل الغلاية.
  • وهكذا…

ما هي البرمجة الوظيفية؟

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

وما هي القواعد التي تؤدي إلى كود وظيفي؟ يمكن شرح البرمجة الوظيفية ببساطة باتباع هذين القانونين في الكود الخاص بك:

  1. تصميم برنامجك باستخدام دوال نقية ومعزولة.
  2. تجنب التغيير (mutability) والآثار الجانبية (side-effects).

دعنا نتعمق في ذلك.

1. تصميم برنامجك باستخدام دوال نقية ومعزولة

لنبدأ من البداية، يعتمد الكود الوظيفي بشكل كبير على عدة أمور:

الدوال النقية (Pure Functions)

الدالة النقية هي دالة تحقق شرطين أساسيين:

  1. نفس المدخلات تعطي دائمًا نفس المخرجات (خاصية idempotence)، أي أنها لا تعتمد على أي حالة خارجية متغيرة.
  2. ليس لها آثار جانبية (no side effects).

الدالة idempotent هي تلك التي عند إعادة تطبيق نتائجها على نفس الدالة مرة أخرى، لا تنتج نتيجة مختلفة. لنأخذ مثال دالة Math.abs() في JavaScript:

Math.abs('-1'); // 1
Math.abs(-1);  // 1
Math.abs(null); // 0
Math.abs(Math.abs(Math.abs('-1'))); // لا تزال تعيد 1
Math.abs(Math.abs(Math.abs(Math.abs('-1')))); // لا تزال تعيد 1

الآثار الجانبية تحدث عندما يتفاعل الكود الخاص بك مع حالة خارجية قابلة للتغيير (mutable state) (يقرأ منها أو يكتب إليها). الحالة الخارجية القابلة للتغيير هي حرفيًا أي شيء خارج الدالة من شأنه أن يغير البيانات في برنامجك. هل قمت بتعيين دالة؟ هل قمت بتعيين قيمة منطقية (Boolean) لكائن؟ هل حذفت خصائص من كائن؟ كل هذه تغييرات على الحالة خارج دالتك.

let available = false;

function setAvailability() {
  available = true;
}

في المثال أعلاه، الدالة setAvailability() ليست نقية لأنها تغير المتغير available الذي يقع خارج نطاقها، وهذا يُعد أثرًا جانبيًا.

الدوال المعزولة (Isolated Functions)

لا يوجد اعتماد على حالة البرنامج، والذي يتضمن المتغيرات العامة (global variables) المعرضة للتغيير. سنناقش هذا بشكل أكبر، ولكن أي شيء تحتاجه يجب أن يمرر إلى الدالة كـ argument (وسيط). هذا يجعل تبعياتك (الأشياء التي تحتاجها الدالة لأداء وظيفتها) أوضح بكثير وأسهل في الاكتشاف.

لماذا نتبع هذه المبادئ؟

قد يبدو هذا وكأنه الكثير من القيود التي تجعل الكود الخاص بك صعبًا بشكل غير ضروري. لكنها ليست قيودًا، بل هي إرشادات تحاول منعك من الوقوع في أنماط تؤدي عادةً إلى الأخطاء. عندما لا تقوم بتغيير تنفيذ الكود الخاص بك، أو تقسيم الكود الخاص بك باستخدام عبارات if بناءً على حالة Boolean يتم تعيينها من أماكن متعددة في الكود الخاص بك، فإنك تجعل الكود أكثر قابلية للتنبؤ وأسهل في فهم ما يحدث.

عندما تتبع النموذج الوظيفي، ستجد أن ترتيب تنفيذ الكود الخاص بك لا يهم كثيرًا. لهذا العديد من الفوائد – أحدها، على سبيل المثال، أنه لتكرار خطأ ما، لا تحتاج إلى معرفة بالضبط ما كانت عليه حالة كل Boolean و Object قبل تشغيل دوالك. طالما أن لديك مكدس استدعاء (call stack) (أنت تعرف ما هي الدالة التي تعمل/عملت قبلك)، يمكنها تكرار الأخطاء وحلها بسهولة أكبر.

قابلية إعادة الاستخدام من خلال الدوال عالية الترتيب (Higher-Order Functions)

الدوال التي يمكن تعيينها لمتغير، أو تمريرها إلى دالة أخرى، أو إعادتها من دالة أخرى تمامًا مثل أي قيمة عادية أخرى، تسمى دوال من الدرجة الأولى (first-class functions). في JavaScript، جميع الدوال هي دوال من الدرجة الأولى. تتيح لنا الدوال التي تتمتع بوضع الدرجة الأولى إنشاء دوال عالية الترتيب.

الدالة عالية الترتيب هي دالة إما أن تأخذ دالة كوسيط، أو تعيد دالة، أو كليهما! يمكنك استخدام الدوال عالية الترتيب لتجنب تكرار نفسك في الكود الخاص بك. شيء مثل هذا:

// Here's a non-functional example
const ages = [12, 32, 32, 53]
let finalAge = 0;
for (var i=0; i < ages.length; i++) {
  finalAge += ages[i];
}
console.log(finalAge); // Output: 129

// Here's a functional example
const agesFunctional = [12, 32, 32, 53]
const totalAge = agesFunctional.reduce(function (firstAge, secondAge) {
  return firstAge + secondAge;
});
console.log(totalAge); // Output: 129

دوال المصفوفة المدمجة في JavaScript مثل .map()، .reduce()، و .filter() كلها تقبل دالة كوسيط. إنها أمثلة ممتازة على الدوال عالية الترتيب، حيث إنها تتكرر عبر مصفوفة وتستدعي الدالة التي تلقتها لكل عنصر في المصفوفة. لذا يمكنك القيام بما يلي:

// Here's an example of each
const array = [1, 2, 3];

const mappedArray = array.map(function (element) {
  return element + 1;
});
// mappedArray is [2, 3, 4]

const reduced = array.reduce(function (firstElement, secondElement) {
  return firstElement + secondElement;
});
// reduced is 6

const filteredArray = array.filter(function (element) {
  return element !== 1;
});
// filteredArray is [2, 3]

تمرير نتائج الدوال إلى دوال أخرى، أو حتى تمرير الدوال نفسها، أمر شائع للغاية في الكود الوظيفي. لقد أدرجت هذا الشرح الموجز بسبب مدى تكرار استخدامه. تُستخدم هذه الدوال أيضًا غالبًا لأنها لا تغير الدالة الأساسية (لا يوجد تغيير في الحالة) ولكنها تعمل على نسخة من المصفوفة array الأصلية.

2. تجنب التغيير (Mutability) والآثار الجانبية (Side-Effects)

القاعدة الثانية هي تجنب التغيير – لقد تطرقنا إلى هذا باختصار سابقًا، عندما تحدثنا عن الحد من التغييرات في الحالة الخارجية القابلة للتغيير – والآثار الجانبية. ولكن هنا سنتوسع أكثر. بشكل أساسي، الأمر يرجع إلى هذا: لا تغير الأشياء! بمجرد إنشائها، تصبح غير قابلة للتغيير (immutable) (لا تتغير بمرور الوقت).

var ages = [12, 32, 32, 53]
ages[1] = 12; // لا! هذا تغيير مباشر
ages = []; // لا! هذا تغيير مباشر
ages.push("2"); // لا! هذا تغيير مباشر

إذا كان لا بد من تغيير شيء ما في هياكل البيانات الخاصة بك، فقم بإجراء التغييرات على نسخة منها.

const ages = [12, 32, 32, 53]
const newAges = ages.map(function (age) {
  if (age == 12) {
    return 20;
  } else {
    return age;
  }
});
// newAges is [20, 32, 32, 53]
// ages remains [12, 32, 32, 53]

هل ترى أنني صنعت نسخة مع التغييرات الضرورية؟ يتكرر هذا العنصر مرارًا وتكرارًا: لا تغير الحالة! إذا اتبعنا هذه القاعدة، فسوف نستخدم const بكثرة حتى نعرف أن الأشياء لن تتغير. لكن الأمر يجب أن يتجاوز ذلك. ماذا عن المثال أدناه؟

const changingObject = {
  willChange: 10
}
changingObject.willChange = 10; // لا! هذا تغيير مباشر لخاصية
delete changingObject.willChange; // لا! هذا تغيير مباشر لخاصية

يجب أن تكون خصائص changingObject مقفلة تمامًا. const سيحميك فقط من إعادة تهيئة المتغير نفسه، وليس خصائصه الداخلية.

const obj = Object.freeze({
  cantChange: 'Locked'
});
// الدالة `freeze` تفرض عدم القابلية للتغيير.
obj.cantChange = 0; // لا يغير الكائن!
delete obj.cantChange; // لا يغير الكائن!
obj.addProp = "Gotcha!"; // لا يغير الكائن!

إذا لم نتمكن من تغيير حالة المتغيرات العامة، فعلينا التأكد من:

  • نعلن عن وسائط الدالة (function arguments) – أي عملية حسابية داخل دالة تعتمد فقط على الوسائط، وليس على أي كائن أو متغير عام.
  • لا نغير متغيرًا أو كائنًا – ننشئ متغيرات وكائنات جديدة ونعيدها إذا لزم الأمر من دالة.

اجعل الكود الخاص بك شفافًا مرجعيًا (Referentially Transparent)

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

const greetAuthor = function () {
  return 'Hi Kealan'
}

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

ولكن لماذا؟ لأنه بدلاً من الكائنات والحالة القابلة للتغيير في الكود الخاص بك، تبدأ في الحصول على دوال نقية، بدون تغيير في الحالة. أنت تفهم بوضوح شديد ما تتوقعه من دالتك أن تعيده (لأنها لا تتغير أبدًا، بينما قد تعيد عادةً أنواع بيانات مختلفة اعتمادًا على الحالة خارج الدالة). يمكن أن يساعدك ذلك على فهم التدفق بشكل أفضل، وفهم ما تفعله الدالة بمجرد تصفحها، وأن تكون أكثر دقة في مسؤوليات كل دالة للتوصل إلى أنظمة أفضل فك ارتباطًا.

يمكنك معرفة المزيد عن الشفافية المرجعية هنا.

تجنب التكرار الحلقي (Don’t Iterate)

نأمل، إذا كنت قد انتبهت حتى الآن، أن ترى أننا لا نغير الحالة. لذا، لتوضيح الأمر، تُستبعد حلقات for:

for (let i = 0; i < arr.length; i++) {
  total += arr[i];
}

لأننا نغير حالة متغير هناك. استخدم دالة الترتيب الأعلى map() بدلاً من ذلك، أو دوال أخرى مثل reduce() أو filter() التي تحقق نفس الغرض دون تغيير الحالة.

ميزات إضافية للبرمجة الوظيفية

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

الاستدعاء الذاتي (Recursion) في البرمجة الوظيفية

من الممكن في JavaScript استدعاء دالة من الدالة نفسها. لذا، ما يمكننا فعله دائمًا:

function recurse() {
  recurse();
}

المشكلة في هذا أنه غير مفيد. سيستمر في العمل حتى يتسبب في تعطل متصفحك. لكن فكرة الاستدعاء الذاتي هي أن تستدعي الدالة نفسها من جسم الدالة. لذا، دعنا نرى مثالًا أكثر فائدة:

function recurse(start, end) {
  if (start === end) {
    console.log(end);
    return;
  } else {
    console.log(start);
    return recurse(start + 1, end);
  }
}

recurse(1, 10); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

سيقوم مقتطف الكود هذا بالعد من الوسيط start إلى الوسيط end. ويقوم بذلك عن طريق استدعاء دالته الخاصة مرة أخرى. لذا سيبدو ترتيب ذلك شيئًا كهذا:

مثال على مكدس الاستدعاء لدالة الاستدعاء الذاتي التي تعد من 1 إلى 10

مثال على مكدس الاستدعاء لهذه الدالة المتكررة. أضف مصحح أخطاء (debugger) داخل كتل if لمتابعة هذا إذا لم يكن منطقيًا بالنسبة لك. الاستدعاء الذاتي هو أحد الأدوات التي يمكنك استخدامها للتكرار في البرمجة الوظيفية.

ما الذي يجعل المثال الأول والمثال الثاني مختلفين؟ المثال الثاني يحتوي على ما نسميه "حالة أساسية" (base case). تسمح الحالة الأساسية للدالة بالتوقف في النهاية عن استدعاء نفسها إلى ما لا نهاية. عندما يكون start مساويًا لـ end، يمكننا التوقف عن الاستدعاء الذاتي. كما نعلم أننا قد وصلنا إلى نهاية حلقتنا. ولكن كل استدعاء للدوال يستدعي دالته الخاصة مرة أخرى، ويضيف إلى وسيط الدالة.

المثال البرمجي الذي أدرجته للتو لمثال العد ليس دالة نقية. لماذا؟ لأن console هي حالة! وقمنا بتسجيل سلاسل نصية string إليها. كانت هذه مقدمة موجزة للاستدعاء الذاتي، ولكن لا تتردد في الذهاب هنا لمعرفة المزيد.

لماذا نستخدم الاستدعاء الذاتي؟

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

التقطيع (Currying) في البرمجة الوظيفية

التقطيع (Currying) هو أداة أخرى تستخدم بكثافة في الكود الوظيفي. يشير arity الدالة إلى عدد الوسائط التي تستقبلها.

// دعنا نتحدث عن arity
function arity2(arg1, arg2) {}
// الدالة لها arity 2

function arity0() {}
// الدالة لها arity 0

function arity4(arg1, arg2, arg3, arg4) {}
// الدالة لها arity 4

تقطيع الدالة يحول دالة لها arity أكبر من 1 إلى 1. يقوم بذلك عن طريق إعادة دالة داخلية لأخذ الوسيط التالي. إليك مثال:

function add(firstNum, secondNum) {
  return firstNum + secondNum;
}

// دعنا نقطع هذه الدالة (Curry this function)
function curryAdd(firstNum) {
  return function (secondNum) {
    return firstNum + secondNum;
  }
}

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

لماذا نستخدم التقطيع؟

الفائدة الكبيرة من التقطيع هي عندما تحتاج إلى إعادة استخدام نفس الدالة عدة مرات ولكنك تغير معلمة واحدة فقط (أو أقل). لذا يمكنك حفظ استدعاء الدالة الأول، شيء مثل هذا:

function curryAdd(firstNum) {
  return function (secondNum) {
    return firstNum + secondNum;
  }
}

let add10 = curryAdd(10);
console.log(add10(2)); // يعيد 12

let add20 = curryAdd(20);
console.log(add20(2)); // يعيد 22

يمكن أن يجعل التقطيع الكود الخاص بك أسهل في إعادة الهيكلة (refactor). لا يتعين عليك تغيير أماكن متعددة حيث تقوم بتمرير وسائط دالة خاطئة – فقط المكان الواحد، حيث قمت بربط استدعاء الدالة الأول بالوسيط الخاطئ. كما أنه مفيد إذا لم تتمكن من توفير جميع الوسائط لدالة في وقت واحد. يمكنك فقط إعادة الدالة الأولى لاستدعاء الدالة الداخلية عندما تكون لديك جميع الوسائط لاحقًا.

التطبيق الجزئي (Partial Application) في البرمجة الوظيفية

بالمثل، يعني التطبيق الجزئي أنك تطبق عددًا قليلاً من الوسائط على دالة في كل مرة وتعيد دالة أخرى يتم تطبيقها على المزيد من الوسائط. إليك أفضل مثال وجدته من وثائق MDN:

const module = {
  height: 42,
  getComputedHeight: function (height) {
    return this.height + height;
  }
};

const unboundGetComputedHeight = module.getComputedHeight;
console.log(unboundGetComputedHeight(32)); // يتم استدعاء الدالة في النطاق العام
// المخرجات: NaN
// المخرجات NaN لأن this.height غير معرف (في نطاق النافذة) لذا يقوم بـ
// undefined + 32 الذي يعيد NaN

const boundGetComputedHeight = unboundGetComputedHeight.bind(module);
console.log(boundGetComputedHeight(32)); // المخرجات المتوقعة: 74

تُعد دالة bind() أفضل مثال على التطبيق الجزئي. لماذا؟ لأننا نعيد دالة داخلية يتم تعيينها لـ boundGetComputedHeight التي يتم استدعاؤها، مع تعيين نطاق this بشكل صحيح وتمرير وسيط جديد لاحقًا. لم نقم بتعيين جميع الوسائط مرة واحدة، بل أعدنا دالة لقبول بقية الوسائط.

لماذا نستخدم التطبيق الجزئي؟

يمكنك استخدام التطبيق الجزئي كلما لم تتمكن من تمرير جميع وسائطك مرة واحدة، ولكن يمكنك إعادة دوال function من دوال عالية الترتيب للتعامل مع بقية الوسائط. إنه يوفر مرونة كبيرة في كيفية التعامل مع الدوال ووسائطها.

تركيب الدوال (Function Composition) في البرمجة الوظيفية

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

// إذا كان لدينا هاتين الدالتين
function add10(num) {
  return num + 10;
}

function add100(num) {
  return num + 100;
}

// يمكننا تركيب هاتين الدالتين إلى =>
function composed(num) {
  return add10(add100(num));
}

console.log(composed(1)); // يعيد 111 (1 + 100 + 10)

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

لماذا نستخدم تركيب الدوال؟

يسمح لك التركيب بهيكلة الكود الخاص بك من دوال قابلة لإعادة الاستخدام، لتجنب تكرار نفسك. يمكنك البدء في التعامل مع الدوال ككتل بناء صغيرة يمكنك دمجها معًا لتحقيق مخرجات أكثر تعقيدًا. تصبح هذه بعد ذلك "الوحدات" أو قوة الحساب في برامجك. إنها الكثير من الدوال الصغيرة التي تعمل بشكل عام، وكلها مركبة في دوال أكبر للقيام بالعمل "الحقيقي".

إنها طريقة قوية لتصميم الكود الخاص بك، وتمنعك من إنشاء دوال ضخمة منسوخة وملصقة مع اختلافات طفيفة بينها. يمكن أن يساعدك أيضًا في الاختبار عندما لا يكون الكود الخاص بك مقترنًا بإحكام. ويجعل الكود الخاص بك أكثر قابلية لإعادة الاستخدام. يمكنك فقط تغيير تركيب دوالك أو إضافة المزيد من الدوال الصغيرة إلى التركيب، بدلاً من نسخ ولصق جميع الأكواد في جميع أنحاء قاعدة الكود (عندما تحتاجها للقيام بشيء مشابه ولكن ليس تمامًا مثل دالة أخرى).

المثال أدناه مبسط لمساعدتك على الفهم، ولكن آمل أن ترى قوة تركيب الدوال:

/// لذا إليك مثال حيث يتعين علينا نسخه ولصقه
function add50(num) {
  return num + 50;
}

// حسنًا. الآن نحتاج إلى إضافة 30. ولكننا لا نزال نحتاج في مكان آخر إلى إضافة 50 أيضًا
// لذا نحتاج إلى دالة جديدة
function add30(num) {
  return num + 30;
}

// آه، تغيير في العمل مرة أخرى
function add20(num) {
  return num + 20;
}

// في كل مرة نحتاج فيها إلى تغيير الدالة بشكل طفيف. نحتاج إلى دالة جديدة

// دعنا نستخدم التركيب
// دالتنا النقية الصغيرة والقابلة لإعادة الاستخدام
function add10(num) {
  return num + 10;
}

function add50Composed(num) {
  return add10(add10(add10(add10(add10(num)))));
}

function add30Composed(num) {
  return add10(add10(add10(num)));
}

function add20Composed(num) {
  return add10(add10(num));
}

console.log(add50Composed(1)); // 51
console.log(add30Composed(1)); // 31
console.log(add20Composed(1)); // 21

هل ترى كيف قمنا بتركيب دوال جديدة من دوال أصغر ونقية؟ هذا يوضح كيف يمكن للدوال الصغيرة أن تصبح وحدات بناء قوية.

الخلاصة

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

الكود الوظيفي ليس بالضرورة الأفضل دائمًا، وكذلك الكود كائني التوجه. يُستخدم الكود الوظيفي عمومًا لمشكلات تعتمد على الرياضيات بشكل أكبر مثل تحليل البيانات. كما أنه مفيد جدًا للأنظمة عالية التوفر في الوقت الفعلي (high-availability real-time systems)، مثل الأشياء المكتوبة بلغة Erlang (لغة وظيفية). لكنه يعتمد حقًا على المشكلة.

أشارك مقالاتي على تويتر. إذا استمتعت بهذا المقال، يمكنك قراءة المزيد هناك.

الخلاصة التقنية

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

كيف تتعلم المزيد؟

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

كيالان بار

اقرأ المزيد من المشاركات. إذا كان هذا المقال مفيدًا، شاركه.

تعلم البرمجة مجانًا. لقد ساعد منهج freeCodeCamp مفتوح المصدر أكثر من 40,000 شخص في الحصول على وظائف كمطورين. ابدأ الآن.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *