شفافية مرجعية

شفافية مرجعية
يشير كلا المصطلحين: الشفافية المرجعية و العتامة المرجعية إلى خصائصٍ لأجزاءٍ من برمجيات الحاسوب. حيث يُقال أن تعبيراً حاسوبياً شفافٌ مرجعياً إن أُمْكِنَ استبداله بقيمته بدون تغيير البرنامج (بصيغةٍ أخرى، إنتاج برنامج له نفس تأثيرات ومخرجات على نفس المدخل). في حين يعتبر النقيض لتلك العملية تعبيراً يُطلق عليه «عاتم مرجعياً».
وبينما تكون كل تطبيقات الدوال الوظيفية في الرياضيات شفافةً مرجعياً، ففي برمجيات الحاسوب ليس الوضع هكذا دائماً. فأهمية الشفافية تتمثل في أنها تسمح للمبرمج والمترجم (المصرف) بتعليل وتبرير سلوك أداء برنامجٍ ما. وقد يساعد هذا في إثبات صحة ، تبسيط الخوارزميات، المساعدة في تعديل الكود بدون كسره وتدميره، أو مواءمة الكود باستخدام وسائل التوازي أو إزالة التعابير الفرعية الشائعة .
مما يجعل الشفافية المرجعية أحد مباديء البرمجة الوظيفية؛ حيث لا تتحول سوى الوظائف شفافة أو جلية المرجعية فقط إلى وظائفٍ مكافئةٍ أو مماثلةٍ تخبيء النتائج. فبعض لغات البرمجة توفِّر وسيلةً لضمان الشفافية المرجعية. في حين تُلزم بعض لغات البرمجة الوظيفية وجود الشفافية المرجعية لكل الوظائف جميعها.
وبسبب أن الشفافية المرجعية تتطلب نفس النتائج لمجموعةٍ متاحةٍ من المدخلات في أي نقطةٍ زمنيةٍ، فنتيجةً لذلك يصبح التعبير الجلي أو الشفاف مرجعياً قطعي التعريف.
أمثلة وأمثلة مناقضة
فلو كانت كل الوظائف المدرجة ضمن تعبيرٍ ما هي وظائف نقية، فإن التعبير حينئذٍ يصبح شفافاً مرجعياً. كذلك، فقد يتم إدراج بعض الوظائف غير النقية في التعبير لو تم تجاهل قيمها وكانت آثارها الجانبية غير هامة.
لتأخذ أحد الوظائف التي لا تأخذ أية عواملٍ وتستعيد المدخل من لوحة مفاتيح الحاسوب. وقد يكون استدعاء تلك الوظيفة بصيغة GetInput(). حيث تعتمد قيمة العودة الخاصة بـ GetInput() على ما يكتبه المستخدم، ومن ثم، فإن النداءات أو الاستدعاءات المتعددة لـ GetInput() بعواملٍ متطابقةٍ (القائمة الفارغة) قد يُعيد نفس النتائج. نتيجةً لذلك، فإن صيغة GetInput() ليست محددة وقطعية أو حتى شفافة وجلية مرجعياً.
ومن الأمثلة البارعة كذلك ذلك الخاص بمتغيرٍ عالميٍ (أو متغير مراقب ومحصور ديناميكياً) لمساعدة الوظيفة في حساب النتائج. وبما أن هذا المتغير لا يُعتبر معاملاً ولكنه يمكن تغييره، فإن نتائج الاستدعاءات المتلاحقة للوظيفة قد تختلف حتى لو كانت الباراميترات متطابقة. ونلاحظ أنه ((ليس مسموح في مجال البرمجة الوظيفية بالتقييم التدميري؛ ومن ثم فإن الوظيفة التي تستخدم المتغيرات العالمية (أو المراقبة ديناميكياً) ما زالت تعتبر شفافة وجلية مرجعياً، نتيجة أن تلك المتغيرات لا تتغير)).
كما أن العمليات الحسابية تتسم بأنها شفافة المرجعية: حيث يمكن أن تحل 25 محل 5*5. وفي الواقع، تعتبر كل الدوال الوظيفية في المحسوس الرياضي شفافة المرجعية فدالة sin(x) هي شفافة، حيث أنها دائماً ستعطي نفس نتيجة كل x خاص.
في حين لا تكون التعيينات والواجبات (Assignments) شفافة. فعلى سبيل المثال، تعبير [[سي (لغة برمجة)|لغة السي) x = x + 1 يُغير القيمة المخصصة للمتغير x. فافتراض أن قيمة x مبدئياً هي 10، يسفر عن وجود تقويمين متواليين للتعبير، على الترتيب، 11 و12. وبصورةٍ واضحةٍ، فإن استبدال التعبير x = x + 1 1 بأيٍ من 11 أو 12 يُعطي البرنامج معناً آخراً مختلفاً، ومن ثم لا يعتبر التعبير شفاف المرجعية. على الرغم من ذلك، فاستدعاء دالة وظيفية مثل " int int plusone(int x) {return x+1;} " يعتبر شفافاً، حيث أنها لن تُغير ضمنياً المدخل x ومن ثم ليس لها أي أثرٍ جانبيٍ برمجيٍ.
كما أننا نلاحظ أن وظيفة " today() " ليست بشفافة، حيث أنك لو قمت بتقويمها واستبدلتها بقيمتها (لنقل، «1 يناير، 2001»)، فأنت لا تحصل على نفس النتيجة لو قمت بتشغيل البرنامج أو الوظيفة غداً. ويرجع هذا إلى أنه يعتمد على حالةٍ وهي تتمثل في (الوقت أو الزمن).
التناقض للبرمجة الأمرية
فلو كانت عملية استبدال تعبيرٍ ما بقيمته مسموحٍ بها عند نقطةٍ ما من تنفيذ البرنامج، فإن التعبير ليس بشفافٍ مرجعياً. حيث يعتبر تحديد وترتيب تلك النقاط المتسلسلة (sequence points) الأساس النظري للبرمجة الأمرية، وجزءً من معاني لغة البرمجة الأمرية.
على الرغم من ذلك، بسبب إمكانية تقويم التعبير الشفاف مرجعياً في أي وقتٍ، فليس بالضروري تحديد نقاط التسلسل أو أي ضمانٍ لترتيب التقويم على الإطلاق. فالبرمجة التي يتم تنفيذها بدون تلك الاعتبارات يُطلق عليها برمجة وظيفية بحتة .
و من أحد مزايا كتابة الكود بأسلوبٍ شفافٍ مرجعياً هو وجود مصرفٍ أو مبرمجٍ ذكيٍ، فتحليل الكود الثابت هي مسألةٍ أيسر وأبسط، كما أن تحولات تحسين - الأكواد الأفضل أصبحت ممكنة بصورةٍ أوتوماتيكيةٍ. فعلى سبيل المثال، عندما تتم البرمجة باستخدام لغة السي، ستكون هناك عقوبة أداءٍ (performance penalty) لتضمين استدعاءٍ لدالةٍ مكلفةٍ غاليةٍ داخل حلقةٍ (loop)، حتى لو اُمكن استدعاء الدالة خارج الحلقة بدون تغيير نتائج البرنامج. حيث سيكون المبرمج ملزماً بالعمل حركة الكود (code motion) اليدوية للاستدعاء، ولربما كان ذلك على حساب مقروئية كود المصدر. على الرغم من ذلك، فلو كان المترجم أو المصرف قادراً على تحديد أن استدعاء دالة وظيفية أمراً شفاف مرجعياً، فإنه يستطيع أن يقوم بهذا التحول آلياً.
إلا أن العيب الأساسي للغات التي تدعم الشفافية المرجعية يتمثل في أنها تجعل تعبير العمليات التي تناسب طبيعياً تسلسل الخطوات أسلوب لبرمجة الأمرية أكثر ضعفاً وأقل دقةٍ. إن مثل تلك اللغات غالباً ما تدمج آليات جعل تلك المهام أسهل في أثناء استعادة الجودة الوظيفية البحتة للغة، مثل نحو الجملة المحددة و monad.
ولا يمكن صناعة فروقٍ، مع الشفافية المرجعية، أو التعرف على ها فيما بين مرجعٍ لشيءٍ ما وشيءٍ مماثلٍ آخرٍ. فبدون الشفافية المرجعية، يصبح ذلك الفرق أيسر ويُستخدم في البرامج.
مثال آخر
دعونا نستخدم دالتين كمثالٍ، إحداهما تتسم بأنها معتمة مرجعياً، في حين تكون الأخرى شفافة مرجعياً:
قيمة عالمية= 0;
دالة رقم صحيح rq (رقم صحيح X)
begin
قيمة عالمية= قيمة عالمية+ 1;
استدعاء x + قيمة عالمية;
end
دالة رقم صحيحrt(رقم صحيحx)
begin
استدعاء؛x + 1
end
الدالة rt هي شفافة مرجعياً، والتي تعني أن rt(x) = rt(y) لو كانت x = y. فعلى سبيل المثال، rt(6) == 6 + 1 = 7، rt(4) = 4 + 1 == 5 وهكذا. على الرغم من ذلك، لا نستطيع أن نقول أي شيءٍ على rq بسبب أنها تستخدم متغير عالمي تقوم بتعديله.
وهنا تفسر عتامة rq المرجعية السبب الكامن وراء كون البرامج أكثر صعوبةٍ. فعلى سبيل المثال، قل نحن نرغب في تفسير الجملة الآتية:
العدد الصحيح p = rq(x) + rq(y) * (rq(x) - rq(x))
فقد يرغب أو يميل فردٌ ما في تبسيط تلك الجملة إلى:
العدد الصحيح p = rq(x) + rq(y) * (0)؛
العدد الصحيح p = rq(x) + 0؛
العدد الصحيح p = rq(x)؛
على الرغم من ذلك، فلن يفلح ذلك الأمر مع rq() بسبب أن كل مرة وقوعٍ لـ rq(x) تقيم بقيمةٍ مختلفةٍ. ولتتذكر، أن القيمة العائدة لـ rq مبنية على قيمة عالمية والتي لم تصدر ويتم تعديلها في كل استدعاء لـ rq. وهذا يعني أن الكيانات الرياضية مثل لم تعد مستخدمة.
إلا أن مثل تلك الكيانات الرياضية سيُستخدم للدوال شفافة المرجعية مثل rt.
على الرغم من ذلك، فيمكن استخدام تفسيرٍ أكثر تعقيداً لتبسيط الجملة إلى:
رقم صحيح a = قيمة عالمية; رقم صحيح p = x + a + 1 + (y + a + 2) * (x + a + 3 - (x + a + 4)); قيمة عالمية = قيمة عالمية + 4;
رقم صحيح a = قيمة عالمية; رقم صحيح p = x + a + 1 + (y + a + 2) * (x + a + 3 - x - a - 4)); قيمة عالمية = قيمة عالمية + 4;
رقم صحيح a = قيمة عالمية; رقم صحيح p = x + a + 1 + (y + a + 2) * -1; قيمة عالمية= قيمة عالمية + 4;
رقم صحيح a = قيمة عالمية; رقم صحيح p = x + a + 1 - y - a - 2; قيمة عالمية = قيمة عالمية + 4;
رقم صحيح p = x - y - 1; قيمة عالمية = قيمة عالمية + 4;
ونلاحظ أن هذا يستغرق خطواتٍ أكثر ويتطلب درجةً من التبصر داخل برنامج غير قابل للتطبيق لتحسين المصرف (compiler optimization).
ومن ثم، تسمح الشفافية المرجعية باستخلاص استنتاجات أرقى تؤدي إلى برامج متينة، والتي تسمح بالعثور على علل (bugs) ما كنا نأمل العثور عليها بالفحص، إضافة إلى إمكانية كشف فرص التحسين (optimization).
المصادر
المراجع
حوسبة
نظرية لغات البرمجة