📖 ستجعلك البرمجة الوظيفية مبرمجاً أفضل

أرى شخصياً أن مُصطلح البرمجة الوظيفية مُضلل بعض الشيء، فهو ترجمة مباشرة لـ Functional Programming. إلا أن الـ function في Functional هي الدالة أو التابع الرياضي، وليست الترجمة الحرفية: ‘الوظيفة’.

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

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

int main (void)
{
  return 0;
}

💡 تعليمة الـ ;return 0 تخبر نظام التشغيل أن البرنامج انتهى بدون مشاكل

ماذا نقصد بأن كل شيء هو تابع؟

في لغات البرمجة التقليدية، نتكلم عن متحولات تحتوي قِيماً وتوابع تقوم بحسابات. في هذا المثال تتغير قيمة x لأنه متحوّل تتحول قيمته.

double x = 5; // هو متحول x
       x = Math.pow(x,4); // هو تابع pow

أما في البرمجة الوظيفية فلا يوجد تفريق بين الاثنين، فنقول أن الرمز x هو تابع يُرجع القيمة 5 مثلاً. دائماً خمسة.

x :: Int -- يُرجع قيمة عدد صحيح x
x = 5 -- يرجع دائما 5 x

الكود السابق مكتوب بلغة Haskell، وكود الجافا التالي يُعادله.

int x() {
  return 5;
}

هذا يعني أن المتحولات في البرنامج لاتتغير قيمتها أبداً ما قد يبدو غريباً في بادئ الأمر، الأمر الذي يعني أن البرنامج الوظيفي يمكن تشغيله بالتوازي، فلا آثار جانبية على المتحولات (لأنها لاتتغير).

الكلاسات (Classes) والواجهات (Interfaces) هي اللبنات الأساسية في البرمجة الغرضية (OOP) وتُربط ببعضها بالوراثة. بينما البرمجة الوظيفية تصف العمليات والإجراءات كتوابع على البيانات. وتُربط ببعضها بتركيب الدوال.

مثال

لنكتب معاً برنامجاً وظيفياً يُفلتر مصفوفة أرقام من الأرقام الفردية، أي أنه يَطبع الأرقام الزوجية فقط. سنستخدم لغة جافاسكربت لأنها بسيطة وسهلة الفهم.

نبدأ بتعريف مصفوفة تحتوي الأرقام من 1 لـ 10.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

💡لاحظ أن المصفوفة معرفة كثابت const، لأننا نريد ضمان أنها لن تتغير.

بعدها نقوم بتعريف تابع بسيط يُخبرنا فيما لو كان العدد زوجي أم سلبي.

const isEven = function(number) {
  return number % 2 == 0;
}

كما سبق، نُعرّف isEven كثابت من نوع تابع function يأخذ الرقم number كمُدخل ويُرجع قيمة بوليانية.

💡 كان بإمكاننا تعريف isEven كتابع مباشرة function isEven(number)، ولكن تعريفه كثابت أقرب نحوياً للأسلوب الوظيفي.

وأخيراً نُطبق عملية الفلترة التي تستدعي التابع isEven على كل عنصر من عناصر المصفوفة numbers لترى فيما لو كان يحقق الشرط أم لا.

numbers.filter(isEven);

بما أننا نتبع الأسلوب الوظيفي، فعملية الفلترة filter(isEven) لَن تغيّر من عناصر numbers، مايدعونا لحفظها في ثابت جديد.

const myNumbers = numbers.filter(isEven);

لنقل أنك الآن تريد فلترة العناصر الفردية وحذفها، وضرب العناصر المتبقية بـ 3.

فلنقم بتعريف التابع يضرب العدد بـ 3.

const triple = function(number){
	return number * 3;
}

ماتبقى هو تطبيق التابع triple على نتيجة الفلترة

const myNumbers = numbers.filter(isEven).map(triple);

فيصبح البرنامج الكامل كما يلي:


const isEven = function(number) {
  return number % 2 == 0;
}

const triple = function(number){
  return number * 3;
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const myNumbers = numbers.filter(isEven).map(triple);

console.log(myNumbers);

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

  • مُختصر: كل سطر يقوم بشيء محدد. لاوجود لعمليات معقدة تستلزم عدة سطور.
  • جاف: لا تكرار. مع أن كلاً من filter و map يتشابهان، إلا أنهما مفصولان عن بعضهما. (قارن فيما لو كانا حلقتي for).
  • سهل التعديل: كل شي عبارة عن تابع، فمثلاً تعديل شرط الفلترة filter سهل ومحصور بتابع isEven.
  • لا آثار جانبية: لاتوجد أية متحولات عامة (Global Variables). لاتوجد أية متحولات البتة. المصفوفة numbers لم تتغير بعد كل العمليات عليها.
  • لانعيد اختراع العجلة: حتى أبسط الأمور في البرنامج كحلقة for للمرور على عناصر مصفوفة يمكن أن تكون مكلفة، ولكن باستخدام توابع اللغة الجاهزة كـ filter و map، لا داعي للقلق على أداء برنامجك فهذه التفاصيل تتكفل لك بها لغة البرمجة.

خلاصة

قد تبدو البرمجة الوظيفية مقيِّدة في بادئ الأمر، فانعدام القدرة على تغيير قيم المتحولات يحتاج وقتاً للاعتياد عليه. الأمر الآخر هو عدم وجود مقابل مباشر للبُنى التقليدية كالجمل الشرطية (Conditionals) أو الحلقات (Loops). لكن هذي القيود تأتي مُقابل إيجابيات تفتقدها البرمجة التقليدية. التفكير بالطريقة الوظيفية والكتابة بها (وإن لم تكن بشكل كلّي) سيمرّنك على التفكير بالمتحولات وقيمها التي تتغير، أو فيما يحتاج كودك إلى تبسيط أو إذا ماكان هذا المتحول العام ضرورياً. قد لاتجد نفسك مطلقاً في مشروع بلغة برمجة وظيفية، ولكن الانضباط البرمجي الذي تفرضه البرمجة الوظيفية سيجعل منك مبرمجاً أفضل.