الدوال النقية وغير النقية في البرمجة الوظيفية: ما الفرق بينهما؟

دقائق القراءة: 6
مقارنة بين الدوال النقية والدوال غير النقية في البرمجة الوظيفية

مقدمة: لماذا يهم فهم الدوال النقية وغير النقية؟

عند التعمق في عالم Functional Programming ستصادف كثيراً مصطلحي Pure Functions وImpure Functions. والفرق الجوهري بينهما يرتبط بمفهوم مهم جداً يُعرف باسم Side Effects أو «الآثار الجانبية».

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

ما المقصود بالآثار الجانبية Side Effects؟

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

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

مثال أول: إضافة قيمة جديدة إلى قيمة قديمة

let oldDigit = 5;

function addNumber(newValue) {
  return oldDigit += newValue;
}

في هذا المثال، تستخدم الدالة addNumber() المتغير الخارجي oldDigit. هذا الاستخدام يسبب عدة آثار جانبية مهمة:

  • الاعتماد على حالة خارجية: الدالة لا تعمل باستقلالية، لأنها تحتاج إلى وجود oldDigit حتى تُكمل مهمتها.
  • تعديل بيانات خارجية: الدالة لا تكتفي بإرجاع نتيجة، بل تغيّر أيضاً قيمة oldDigit نفسها.
  • صعوبة التنبؤ بالمخرجات: لا يمكنك معرفة ناتج addNumber() بمجرد قراءة الدالة فقط، لأن الناتج يتأثر بالقيمة الحالية للمتغير oldDigit.

لهذا السبب تُعد هذه الدالة غير حتمية Non-deterministic، لأن النتيجة لا تعتمد فقط على المُدخل newValue، بل تعتمد أيضاً على حالة خارجية متغيرة.

مثال ثانٍ: طباعة نص في وحدة التحكم

function printName() {
  console.log("My name is Oluwatobi Sofela.");
}

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

استخدام console.log() داخل الدالة يجعلها:

  • تعتمد على كائن خارجي هو console.
  • تُحدث أثراً خارجياً يمكن ملاحظته.
  • أقل نقاءً من الدوال التي تكتفي بإرجاع قيمة فقط.

إذن، كلما استعملت داخل الدالة مورداً خارجياً أو أجريت تغييراً خارجياً، فأنت تتعامل مع Side Effects.

ما هي الدالة غير النقية Impure Function؟

الدالة غير النقية هي أي دالة تحتوي على أثر جانبي واحد أو أكثر. أي أنها إما تعتمد على حالة خارجية، أو تُعدّل تلك الحالة، أو تقوم بأمر لا يقتصر على حساب قيمة وإرجاعها.

تأمل المثال التالي:

const myNames = ["Oluwatobi", "Sofela"];

function updateMyName(newName) {
  myNames.push(newName);
  return myNames;
}

الدالة updateMyName() هنا غير نقية لأنها تعدّل المصفوفة الخارجية myNames باستخدام push(). هذا يعني أن الدالة لا تعمل على بياناتها الخاصة فقط، بل تعبث بحالة خارجية موجودة خارج نطاقها.

ما هي الدالة النقية Pure Function؟

الدالة النقية هي الدالة التي لا تحتوي على أي آثار جانبية. بعبارة عملية، يجب أن تعتمد فقط على المُدخلات التي تستقبلها، وأن تُرجع ناتجاً دون تعديل أي شيء خارجها.

انظر إلى هذا المثال:

function updateMyName(newName) {
  const myNames = ["Oluwatobi", "Sofela"];
  myNames[myNames.length] = newName;
  return myNames;
}

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

لماذا تُعد الدوال النقية خياراً أفضل في كثير من الحالات؟

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

1) الاستقلالية وسهولة الفهم

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

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

2) سهولة الاختبار Testing

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

3) قابلية أعلى للتصحيح والصيانة

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

معلومات مهمة يجب معرفتها عن الدوال النقية

نسخ الحالة الخارجية لا يجعل الدالة غير نقية

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

مثال:

const myBio = ["Oluwatobi", "Sofela"];

function updateMyBio(newBio, array) {
  const clonedBio = [...array];
  clonedBio[clonedBio.length] = newBio;
  return clonedBio;
}

console.log(updateMyBio("codesweetly.com", myBio));

في المثال السابق، استخدمت الدالة updateMyBio() عامل النشر spread operator لنسخ المصفوفة المُمرّرة إليها في المتغير clonedBio. ورغم أن المصدر الأصلي هو myBio، فإن الدالة لا تعدّل هذا المصدر، بل تتعامل مع نسخة مستقلة. لذلك تظل دالة نقية.

تجنب التعديل الداخلي قدر الإمكان

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

مثال على دالة نقية تستخدم تغييراً محلياً:

const compBio = ["code", "sweetly"];

function updateCompBio(newBio, array) {
  const clonedBio = [...array];
  clonedBio[clonedBio.length] = newBio;
  return clonedBio;
}

console.log(updateCompBio(".com", compBio));

هذه الدالة ما تزال نقية، لأنها لا تمس الحالة الخارجية. لكن توجد صياغة أفضل وأكثر وضوحاً:

const compBio = ["code", "sweetly"];

function updateCompBio(newBio, array) {
  return [...array, newBio];
}

console.log(updateCompBio(".com", compBio));

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

المدخلات نفسها تعني المخرجات نفسها

من أهم خصائص الدالة النقية أنها تُرجع القيمة نفسها دائماً إذا استقبلت مجموعة المدخلات نفسها، مهما كررت استدعاءها. هذه السمة تجعل السلوك قابلاً للتنبؤ بشكل ممتاز، وهي من الأسباب الرئيسية التي تجعل هذا النمط محبوباً في Functional Programming.

مقارنة سريعة بين الدوال النقية وغير النقية

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

متى تستخدم كل نوع؟

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

  • القراءة من قاعدة بيانات.
  • إرسال طلبات API.
  • الكتابة في الملفات.
  • الطباعة باستخدام console.log().
  • تحديث واجهة المستخدم.

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

أفضل الممارسات عند كتابة دوال نقية

  1. مرّر كل القيم المطلوبة عبر معاملات الدالة parameters.
  2. تجنب استخدام المتغيرات العامة global variables داخل الدوال.
  3. لا تعدّل الكائنات أو المصفوفات الخارجية مباشرة.
  4. أعد قيماً جديدة بدلاً من تغيير القيم الأصلية.
  5. افصل منطق الحساب عن منطق الإدخال والإخراج.

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

إذا كانت الدالة تعتمد فقط على مدخلاتها وتُرجع نتيجة دون أي تأثير خارجي، فهي دالة نقية Pure Function. أما إذا اعتمدت على حالة خارجية أو غيّرتها أو نفذت عملية ذات أثر جانبي، فهي دالة غير نقية Impure Function. عملياً، كلما زادت نسبة الدوال النقية في مشروعك، أصبحت الشيفرة أوضح وأسهل في الاختبار وأفضل في الصيانة. الرأي التقني المختصر: اجعل منطق الأعمال Business Logic نقياً قدر الإمكان، واترك الآثار الجانبية محصورة في أماكن محددة وواضحة داخل التطبيق.

اترك تعليقاً

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