مجال (علم الحاسوب)

مجال (علم الحاسوب)
في علم الحاسوب، يعتبر المجال لمعرِّف ما (بالإنكليزية Scope) هو الجزء من البرنامج الحاسوبي الذي يمكن فيه استخدام المعرّف -و هو اسم يعود إلى كيان ما في البرنامج- للوصول إلى الكيان المقترن به. لذا، فإن المجال هو السياق في البرنامج الذي يكون فيه المعرّف صالح للاستعمال والوصول إلى الكيان المقترن به. أي بمعنى أخر: عندما يكون الكيان مرئي. في معظم الأحيان، يستخدم مصطلح «المجال» للحديث عن متى يمكن استخدام اسم متغير ما، له «حدود» داخل البرنامج بانتهاء هذا الحد ينتهي عمله ممكن أن نستخدم نفس اسم المتغير في كيان اخر ضمن حدود أخرى في نفس البرنامج لغرض أخر أو قد يكون إلى اللاشئ. ولكنه ينطبق أيضا على كيانات أخرى مثل: الأنواع (أو الأصناف) والوظائف. يعتبر المجال أمرا مهما لتحليل الاسم (name resolution) والذي هو بدوره أساسي لدلالات اللغة (language semantics): فالمعنى الكلي للبرنامج يعتمد على معانى الكلمات المكونة له منفردة. تحليل الاسم (و الذي يشمل أيضا المجال) يختلف من لغة برمجة إلى أخرى، وحتى في البرنامج الواحد فهو يختلف حسب نوع الكيان. تعتبر قواعد المجال -إضافة إلى قواعد أسماء المكتبات- حاسمة ومصيرية في البرمجة التركيبية، كنتيجة لهذا، فإن أي تغير على أي جزء من البرنامج لا يضر بالأجزاء الأخرى التي ليس لها علاقة.
وعمليًا معظم لغات البرمجة، تشير «جزء من البرنامج» إلى «جزء من شفرة المصدر (مساحة النص)»، ويعرف بالنطاق المعجمى"ٍStatic scop" يشير «جزء من البرنامج» ماقبل مرحلة التنفيذ أما الذي يشير إلى «جزء من وقت التشغيل (الفترة الزمنية أثناء التنفيذ)»، ويعرف بالنطاق الديناميكي"Dynamic scope." هذان المصطلحان مضللان إلى حد ما - إنهما يسيئان استخدام المصطلحات التقنية، ولكن التمييز نفسه دقيق ودقيق.
النطاق المعجمي «الثابت» (Lexical static scope)هو التركيز الرئيسي في هذه المقالة، مع فهم النطاق الديناميكي (Dynamic scope).
تعريف
التعريف الدقيق للنطاق لثابت "Static scope" لاسم (معرف) إنه «جزء من شفرة المصدر التي ينطبق عليها ربط اسم مع كيان» - ولم يتغير تقريبًا عن تعريفه لعام 1960 في مواصفات ALGOL 60. يتبع مواصفات لغة التمثيلية.
(ALGOL 60 (1960):
يتم تمييز الأنواع التالية من الكميات: المتغيرات البسيطة، المصفوفات، الملصقات، المفاتيح، والإجراءات. نطاق الكمية هو مجموعة من العبارات والتعبيرات التي يكون فيها تعريف المعرّف المرتبط بهذه الكمية صالحًا.(C (2007 :C
يمكن أن يشير المعرّف إلى كائن؛ وظيفة؛ علامة أو عضو في هيكل أو اتحاد أو تعداد؛ اسم typedef؛ اسم العلامة؛ يمكن أن يشير المعرف نفسه إلى كيانات مختلفة في نقاط مختلفة في البرنامج. بالنسبة لكل كيان مختلف يحدده المعرف، يكون المعرّف مرئيًا (أي يمكن استخدامه) فقط داخل منطقة من نص البرنامج تسمى نطاقه.
(Go (2013 :
يربط التصريح معرفًا غير فارغ إلى ثابت أو نوع أو متغير أو دالة أو تسمية أو حزمة. نطاق المعرّف المعلن هو مدى النص المصدر الذي يشير فيه المعرِّف إلى الثابت المحدد أو النوع أو المتغير أو الوظيفة أو التسمية أو الحزمة.
يشير «المجال» الأكثر شيوعًا إلى متى يمكن أن يشير اسم معين إلى متغير معين - عندما يكون للإعلان تأثير - ولكن يمكن أن ينطبق أيضًا على الكيانات الأخرى، مثل الوظائف والأنواع والفئات والتسميات والثوابت والتعدادات.
النطاق المعقول مقابل النطاق الديناميكي (Lexical "Static" scope vs. dynamic scope)
التمييز الأساسي في تحديد النطاق هو ما يعني «جزء من البرنامج». في اللغات ذات النطاق المعجمي (يطلق عليها أيضًا النطاق الثابت)، يعتمد تحليل الاسم على الموقع في شفرة المصدر والسياق المعجمى، والذي يتم تحديده من خلال مكان تعريف المتغير أو الوظيفة المحددة. في المقابل، في اللغات ذات النطاق الديناميكي، يعتمد تحليل الاسم على حالة البرنامج عند مواجهة الاسم الذي يتم تحديده بواسطة سياق التنفيذ أو سياق الاتصال. في الممارسة العملية، باستخدام النطاق المعجمى يتم حل تعريف المتغير من خلال البحث عن الكتلة أو الوظيفة التي يحتوي عليها، ثم إذا فشل ذلك في البحث عن الكتلة الخارجية، وهكذا، في حين يتم البحث عن وظيفة الاستدعاء مع النطاق الديناميكي، يتم البحث عن الوظيفة المطلوبة، وظيفة، وما إلى ذلك، في كلا النظامين، نبحث أولاً عن تعريف محلي لمتغير.
تستخدم معظم اللغات الحديثة تحديد المدى المعجمي «الثابت»(Static scope)للمتغيرات والوظائف، على الرغم من استخدام النطاق الديناميكي (Dynsamic scope)في بعض اللغات، لا سيما بعض لهجات Lisp ، وبعض لغات «البرمجة النصية» مثل Perl ، وبعض لغات القالب. حتى في اللغات ذات النطاق lexically ، نطاق قد تكون الإغلاقات مربكة لغير المطلعين، لأنها تعتمد على السياق المعجمى«الثابت» حيث يتم تحديد الإغلاق، وليس حيث يتم استدعاؤه.
يمكن تحديد الدقة المعجمية في وقت التحويل البرمجي، ويُعرف أيضًا بالربط المبكر، بينما يمكن تحديد الدقة الديناميكية بشكل عام فقط في وقت التشغيل، وبالتالي تُعرف بالربط المتأخر.
مفاهيم ذات صلة
في البرمجة الشيئية، يحدد الإرسال الديناميكي طريقة كائن في وقت التشغيل، على الرغم من أن ربط الاسم الفعلي يتم في وقت التحويل البرمجي أو وقت التشغيل يعتمد على اللغة. إن الفحص الديناميكي بحكم الواقع أمر شائع في اللغات الكبرى، والتي لا تفعل مباشرة تحليل الاسم، ولكن بدلاً من ذلك توسع في مكانه.
تستخدم بعض اطارات البرمجة مثل AngularJS مصطلح «نطاق» لتعني شيئًا مختلفًا تمامًا عن كيفية استخدامه في هذه المقالة. في هذا الإطار، يكون النطاق مجرد كائن من لغة البرمجة التي يستخدمونها (JavaScript في حالة AngularJS) التي يتم استخدامها بطرق معينة بواسطة إطار العمل لمحاكاة النطاق الديناميكي بلغة تستخدم نطاقًا معجميًا «ثابتا» لمتغيراتها. يمكن أن تكون نطاقات AngularJS نفسها في نطاقها أو خارج نطاقها (باستخدام المعنى المعتاد للمصطلح) في أي جزء من البرنامج، مع اتباع القواعد المعتادة للنطاق المتغير للغة مثل أي كائن آخر، واستخدام ميراثها وقواعد الاستبعاد. في سياق AngularJS ، في بعض الأحيان يتم استخدام مصطلح «نطاق $» (مع علامة الدولار) لتجنب الارتباك، ولكن استخدام علامة الدولار في أسماء المتغيرات غالبا ما يتم تثبيط من قبل أدلة الأسلوب.
الاستخدام
النطاق «المجال» هو مكون مهم في تحليل الاسم، والذي يعتبر بدوره أساسيًا لدلالات اللغة. يختلف تحليل الاسم (بما في ذلك النطاق) بين لغات البرمجة، وضمن لغة برمجة، يختلف ذلك باختلاف نوع الكيان؛ تسمى قواعد النطاق باسم قواعد النطاق أو قواعد تحديد النطاق. إلى جانب مساحات الأسماء، تعتبر قواعد تحديد النطاق حاسمة في البرمجة المعيارية، لذا لا يؤدي أي تغيير في جزء واحد من البرنامج إلى كسر جزء غير مرتبط منه.
نظرة عامة
عند مناقشة النطاق، هناك ثلاثة مفاهيم أساسية: النطاق، والمدى، والسياق. غالبًا ما يتم الخلط بين «النطاق» و «السياق» بشكل خاص: النطاق عبارة عن خاصية لمعرّف، ويتم إصلاحه، في حين أن السياق عبارة عن خاصية لبرنامج، يختلف باختلاف الموقع. بتعبير أدق، السياق هو خاصية لموضع في البرنامج، إما موضع في شفرة المصدر (سياق معجم) أو نقطة خلال وقت التشغيل (سياق التنفيذ، سياق وقت التشغيل، أو سياق الاتصال). يتكون سياق التنفيذ من سياق معجم (عند نقطة التنفيذ الحالية) بالإضافة إلى حالة وقت تشغيل إضافية مثل مكدس الاستدعاءات. وهكذا، عندما تكون نقطة تنفيذ البرنامج في نطاق اسم المتغير، يكون «المتغير (الاسم) السياق»(يعني«في السياق عند هذه النقطة»)، وعندما تخرج نقطة التنفيذ«نطاق (اسم) متغير»، مثل بالعودة من دالة، «يخرج المتغير (الاسم) خارج السياق» تحدث بشكل ضيق، أثناء التنفيذ، يدخل البرنامج ويخرج من نطاقات مختلفة، وعند نقطة في التنفيذ، تكون المعرفات «في سياق» أو «ليس في سياق»، ومن ثم، فإن المعرفات «تأتي في سياق» أو «تخرج عن سياقها». كما يدخل البرنامج أو يخرج من النطاق - ولكن في الممارسة العملية هو أكثر مرونة.
نطاق هو مفهوم مستوى التعليمات البرمجية المصدر، وخصائص معرفات، وخاصة أسماء متغير أو وظيفة - المعرفات في شفرة المصدر هي إشارات إلى الكيانات في البرنامج - ويشكل جزءا من سلوك المترجم أو مترجم للغة. على هذا النحو، فإن قضايا النطاق تشبه المؤشرات، والتي هي نوع من أنواع المراجع المستخدمة في البرامج بشكل عام. إن استخدام قيمة متغير عندما يكون الاسم في السياق ولكن المتغير غير مهيأ هو مماثل لإلغاء الإشارة (الوصول إلى قيمة) مؤشر wild ، لأنه غير معرف. ومع ذلك، نظرًا لأن المتغيرات لا يتم إتلافها حتى تخرج عن سياقها، لا يوجد تناظري لمؤشر متدلي.
بالنسبة للكيانات مثل المتغيرات، النطاق هو مجموعة فرعية من العمر (يُعرف أيضًا بمدى) - يمكن أن يشير الاسم فقط إلى متغير موجود (ربما مع قيمة غير محددة)، ولكن المتغيرات الموجودة ليست بالضرورة مرئية: قد يوجد متغير ولكن لا يمكن الوصول إليها (يتم تخزين القيمة ولكن لا يشار إليها في سياق معين)، أو يمكن الوصول إليها ولكن ليس عن طريق الاسم المحدد، وفي هذه الحالة يكون خارج السياق (البرنامج «خارج نطاق الاسم»). في حالات أخرى، «العمر» غير ذي صلة - العلامة (المسمى بموضع في شفرة المصدر) لها عمر متطابق مع البرنامج (للغات المجمعة بشكل ثابت)، ولكنها قد تكون داخل أو خارج السياق في نقطة معينة في البرنامج، وبالمثل للمتغيرات الثابتة - المتغير العام الثابت في سياق البرنامج بأكمله، بينما يكون المتغير المحلي الثابت في السياق فقط داخل دالة أو سياق محلي آخر، ولكن كلاهما له عمر كامل لتشغيل البرنامج.
ويعرف تحديد الكيان الذي يشير إليه المعرف باسم تحليل الاسم أو ربط الاسم (خاصة في البرمجة الشيئية)، ويختلف بين اللغات. في ضوء معرف معين، تقوم اللغة (بشكل صحيح، المحول البرمجي أو المترجم) بفحص جميع الكيانات الموجودة في سياق التطابقات؛ في حالة الغموض (كيانان لهما نفس الاسم، مثل متغير عالمي ومحلي بنفس الاسم)، يتم استخدام قواعد تحليل الاسم لتمييزها. في كثير من الأحيان، تعتمد دقة الاسم على قاعدة «داخلية إلى خارجية»، مثل قاعدة Python LEGB (محلي، مضمن، عالمي، مدمج): تتضمّن الأسماء ضمنيًا إلى أضيق سياق ذي صلة. في بعض الحالات، يمكن تحديد دقة الاسم بوضوح، مثل الكلمات الرئيسية العامة وغير المحلية في بايثون؛ في حالات أخرى لا يمكن تجاوز القواعد الافتراضية.
عندما يكون هناك معرّفان متطابقان في السياق في نفس الوقت، في إشارة إلى الكيانات المختلفة، يقول أحدهم أن إخفاء الاسم يحدث، حيث يكون الاسم ذو الأولوية الأعلى (عادةً أعمق) هو «إخفاء» اسم الأقل أولوية. على مستوى المتغيرات، يعرف هذا باسم التظليل المتغير. نظرًا لاحتمالية حدوث أخطاء منطقية من الإخفاء، فإن بعض اللغات لا تسمح أو تمنع الإخفاء، وترفع خطأ أو تحذيرًا في وقت الترجمة أو وقت التشغيل.
تحتوي لغات البرمجة المختلفة على قواعد تحديد نطاق مختلفة لأنواع مختلفة من الإعلانات والمعرفات. هذه القواعد تحديد نطاق لها تأثير كبير على دلالات اللغة، وبالتالي، على سلوك وصحة البرامج. في اللغات مثل++ C ، لا يحتوي الوصول إلى متغير غير منضم على دلالات محددة بدقة وقد يؤدي إلى سلوك غير محدد، مشابهًا للإشارة إلى مؤشر متدلي ؛ والتوضيحات أو المعرفات المستخدمة خارج نطاقها ستؤدي إلى حدوث أخطاء في بناء الجملة.
ترتبط النطاقات بشكل متكرر ببنى لغة أخرى ويتم تحديدها ضمنيًا، ولكن العديد من اللغات تقدم أيضًا بنيات خصيصًا للتحكم في النطاق.
مستويات النطاق
يمكن أن يختلف النطاق من تعبير واحد إلى أخر من كل البرنامج، مع وجود العديد من التدرجات الممكنة بينهما. أبسط قاعدة لتحديد النطاق هي النطاق العالمي - جميع الكيانات مرئية طوال البرنامج بأكمله. قاعدة الفحص المعيارية الأساسية هي تحديد النطاق على مستويين، مع نطاق عام (global scope) في أي مكان في البرنامج، ونطاق محلي (Local scope) ضمن إحدى الوظائف. تسمح البرمجة المعيارية الأكثر تعقيدًا بنطاق وحدة منفصلة، حيث تكون الأسماء مرئية داخل الوحدة (خاصة إلى الوحدة) ولكنها غير مرئية خارجها. داخل بعض الوظائف، تسمح بعض اللغات ، مثل C ، بنطاق الحظر لتقييد النطاق لمجموعة فرعية من الدالة ؛ الآخرين ، ولا سيما اللغات الوظيفية ، تسمح بنطاق التعبير ، لتقييد النطاق إلى تعبير واحد. تتضمن النطاقات الأخرى نطاق الملف (لا سيما في C)، والذي يعمل بشكل مشابه لنطاق الوحدة النمطية ، ويمنع النطاق خارج الوظائف (خاصة في Perl).
المشكلة الدقيقة هي بالضبط عندما يبدأ النطاق وينتهي. في بعض اللغات ، كما هو الحال في C ، يبدأ النطاق في الإعلان وبالتالي يمكن أن يكون للأسماء المختلفة المُعلنة داخل كتلة معينة نطاقات مختلفة. ويتطلب ذلك الإعلان عن الوظائف قبل الاستخدام ، على الرغم من عدم تحديدها بالضرورة ، ويتطلب الإعلان المسبق في بعض الحالات ، لا سيما عن التكرار المتبادل. بلغات أخرى ، مثل JavaScript أو Python ، يبدأ نطاق الاسم في بداية الكتلة المعنية (مثل بداية دالة)، بغض النظر عن المكان الذي تم تعريفه ، ويكون لكل الأسماء الموجودة داخل كتلة معينة نفس النطاق ؛ في JavaScript يُعرف هذا باسم الرفع المتغير. ومع ذلك ، عندما يختلف الاسم إلى قيمة ، يختلف سلوك أسماء السياق التي لها قيمة غير محددة: في بايثون ، يؤدي استخدام المتغيرات غير المحددة إلى حدوث خطأ في وقت التشغيل ، بينما تكون متغيرات جافا سكريبت غير القابلة للاستخدام قابلة للاستخدام (مع قيمة غير محددة)، ولكن يتم أيضًا رفع الإعلانات الدالة إلى الجزء العلوي من الدالة المحتوية واستخدامها خلال الدالة.
نطاق التعبير
تقدم العديد من اللغات ، وخاصة اللغات الوظيفية ، ميزة تسمى «السماح بالتعبيرات»، والتي تسمح لنطاق الإعلان أن يكون تعبيرًا واحدًا. على سبيل المثال ، هناك حاجة إلى قيمة وسيطة للحساب. على سبيل المثال ، في ML قياسي
if f() returns12, then let val x = f() in x * x end
هو تعبير يتم تقييمه إلى 144 ، باستخدام متغير مؤقت المسمى x لتجنب استدعاء مرتين. بعض اللغات ذات نطاق الكتلة تقترب من هذه الوظيفة من خلال توفير بناء جملة لكتلة يتم تضمينها في تعبير؛ على سبيل المثال ، يمكن كتابة تعبير ML القياسي أعلاه في Perl كـ
do { my $x = f(); $x * $x }, or in مترجم شفرة حاسوب جنو as ({ int x = f(); x * x; }).
في بايثون " "Python ، المتغيرات المساعدة في تعبيرات المولدات وفهم القوائم (في Python 3) لها نطاق تعبير.
في C ، يكون لأسماء المتغيرات في النموذج الأولي وظيفة نطاق تعبير ، يعرف في هذا السياق كنطاق بروتوكول وظيفي. بما أن أسماء المتغيرات في النموذج الأولي لا يشار إليها (قد تكون مختلفة في التعريف الفعلي) - فهي مجرد دمى - غالبًا ما يتم حذفها ، على الرغم من أنها قد تستخدم في توليد التوثيق.
كتلة النطاق
تسمح العديد من لغات البرمجة المهيكلة للكتل ، وليس كلها ، بتقييد النطاق على كتلة ، والتي تعرف باسم نطاق الكتلة. بدأ هذا بـ ALGOL 60، حيث «[...] الإعلان ... صحيح فقط لهذه الكتلة.»، واليوم يرتبط بشكل خاص باللغات في عائلة باسكال وجيم وتقاليدها. غالباً ما يتم احتواء هذه الكتلة داخل دالة ، وبالتالي تقييد النطاق إلى جزء من الدالة ، ولكن في بعض الحالات ، مثل Perl ، قد لا تكون الكتلة داخل دالة.
unsigned int sum_of_squares(const unsigned int N) {
unsigned int ret = 0;
for (unsigned int n = 1; n <= N; n++) {
const unsigned int n_squared = n * n;
ret += n_squared;
}
return ret;
}
المثال المعتمد لاستخدام نطاق المنع هو الكود C الموضح هنا ، حيث يتم تحديد نطاق متغيرين للحلقة: المتغير n، الذي تتم تهيئته مرة واحدة ويزداد في كل تكرار للحلقة ، والمتغير المساعد n_squared ، والذي تتم التهيئة في كل تكرار. والغرض من ذلك هو تجنب إضافة متغيرات إلى نطاق الدوال ذي الصلة فقط بكتلة معينة - على سبيل المثال ، يمنع الأخطاء حيث تم بالفعل تعيين متغير الحلقة العامة الذي قمت بطريق الخطأ إلى قيمة أخرى. في هذا المثال ، لن يتم تعيين تعبير n * nبشكل عام لمتغير مساعد ، وببساطة يكون نص الحلقة مكتوبًا ret += n * n ولكن في الأمثلة الأكثر تعقيدًا تكون المتغيرات المساعدة مفيدة.
تستخدم الكتل في المقام الأول لتدفق التحكم ، كما هو الحال في حالة ، بينما ، وللحلقة ، وفي هذه الحالات ، فإن نطاق الكتلة يعني أن نطاق المتغير يعتمد على بنية تدفق تنفيذ الدالة. ومع ذلك ، فإن اللغات ذات نطاق الكتل تسمح عادةً باستخدام الكتل «المجردة»، التي يكون غرضها الوحيد هو السماح بالتحكم الدقيق في النطاق المتغير. على سبيل المثال ، يمكن تعريف المتغير الإضافي في كتلة ، ثم استخدامه (على سبيل المثال ، إضافة إلى متغير مع نطاق الوظيفة) والتخلص منه عند انتهاء الكتلة ، أو قد تكون حلقة أثناء محاطة بكتلة تقوم بتهيئة المتغيرات المستخدمة داخل الحلقة يجب أن يتم تهيئتها مرة واحدة فقط.
إن دقة العديد من لغات البرمجة ، مثل Algol 68 و C (كما هو موضح في هذا المثال والموحدة القياسية منذ C99)، هي أنه يمكن الإعلان عن متغيرات نطاق الكتلة ليس فقط داخل جسم الكتلة بل أيضًا ضمن بيان التحكم ، إذا أي. ويماثل ذلك مع معلمات الدالة ، التي يتم تعريفها في تعريف الدالة (قبل بدء تشغيل كتلة الدالة)، وفي نطاق جسم الوظيفة بالكامل. يُستخدم هذا بشكل أساسي في الحلقات ، التي تحتوي على عبارة التهيئة منفصلة عن حالة الحلقة ، على عكس الحلقات ، وهي لغة شائعة.
يمكن استخدام نطاق الحظر للتظليل. في هذا المثال ، داخل الكتلة ، يمكن أيضًا تسمية المتغير المساعد n ، وتظليل اسم المعلمة ، ولكن هذا يعتبر أسلوبًا سيئًا نظرًا لاحتمالية حدوث أخطاء. علاوة على ذلك ، بعض المتحدرين من C ، مثل Java و C # ، على الرغم من وجود دعم لنطاق الكتلة (حيث يمكن جعل متغير محلي يخرج من النطاق قبل نهاية الدالة)، لا تسمح لمتغير محلي واحد بإخفاء آخر . في مثل هذه اللغات ، قد يؤدي محاولة إعلان n الثاني إلى حدوث خطأ في بناء الجملة ، وسيتعين إعادة تسمية أحد المتغيرات n.
إذا تم استخدام قالب لتعيين قيمة متغير ، يتطلب نطاق الكتلة أن يتم الإعلان عن المتغير خارج الكتلة. هذا يعقد استخدام البيانات الشرطية مع مهمة واحدة. على سبيل المثال ، في بايثون ، التي لا تستخدم نطاق الحظر ، يمكن للمرء أن يهيئ المتغير على هذا النحو:
if c:
a = 'foo'
else:
a = ''
حيث يمكن الوصول إليها بعد العبارة if.
في Perl ، الذي يحتوي على نطاق الحظر ، يتطلب ذلك بدلاً من ذلك تحديد المتغير قبل الكتلة:
my $a;
if (c) {
$a = 'foo';
} else {
$a = '';
}
غالبًا ما يتم إعادة الكتابة بدلاً من ذلك باستخدام تعيين متعدد ، مع تهيئة المتغير إلى قيمة افتراضية. في بايثون (حيث لا يكون ضروريًا) على هذا النحو:
a = ''
if c:
a = 'foo'
بينما في Perl سيكون هكذا:
my $a = '';
if (c) {
$a = 'foo';
}
في حالة تخصيص متغير واحد ، يكون البديل هو استخدام المشغل الثلاثي لتجنب كتلة ، ولكن هذا ليس ممكنًا بشكل عام لتخصيصات متعددة ومتغيرة ، ويصعب قراءته بالنسبة للمنطق المعقّد.
هذه مشكلة أكثر أهمية في C ، خاصةً لتخصيص السلسلة ، حيث يمكن أن تقوم تهيئة السلسلة تلقائيًا بتخصيص الذاكرة ، بينما يتطلب تعيين سلسلة إلى متغير تم تهيئته بالفعل تخصيص الذاكرة ونسخة سلسلة والتحقق من نجاحها.
sub increment_counter
{
my $counter = 0;
return sub
{
return ++$counter;
}
}
تسمح بعض اللغات بمفهوم نطاق الكتلة ليتم تطبيقه ، بدرجات متفاوتة ، خارج الدالة. على سبيل المثال ، في مقطع Perl على اليمين ، $ counter هو اسم متغير بنطاق كتلة (بسبب استخدام الكلمة الرئيسية الخاصة بي)، بينما increment_counter هو اسم دالة بنطاق عالمي. ستزيد كل مكالمة إلى increment_counter من قيمة العداد بالدولار الواحد وتعيد القيمة الجديدة. يمكن أن يؤدي الرمز خارج هذا الحظر إلى الاتصال بـ increment_counter ، ولكن لا يمكنه الحصول على قيمة العداد بالدولار أو تغييره. هذا المصطلح يسمح للمرء بتعريف الإغلاق في perl.
نطاق الوظيفة
تقدم معظم لغات البرمجة الشائعة طريقة لإنشاء متغير محلي "Local varibale" في وظيفة أو روتين فرعي: متغير ينتهي نطاقه (يخرج من السياق) عندما تعود الدالة. في معظم الحالات ، يكون عمر المتغير هو مدة استدعاء الدالة - وهو متغير تلقائي "automatic variable"، يتم إنشاؤه عند بدء الدالة (أو يتم الإعلان عن المتغير)، يتم إتلافها عند إرجاع الدالة - في حين يكون نطاق المتغير داخل وظيفة ، على الرغم من أن معنى «داخل» يعتمد على ما إذا كان scoping معجمية أو ديناميكية. ومع ذلك ، فإن بعض اللغات ، مثل C ، توفر أيضًا للمتغيرات المحلية الثابتة ، حيث يمثل عمر المتغير عمر البرنامج بأكمله ، ولكن المتغير يكون فقط في السياق عندما يكون داخل الدالة. في حالة المتغيرات المحلية الثابتة ، يتم إنشاء المتغير عند تهيئة البرنامج ، ويتم إتلافه فقط عند إنهاء البرنامج ، كما هو الحال مع متغير عام ثابت "static global variable"، ولكن في سياق داخل دالة فقط ، مثل متغير محلي تلقائي " automatic local variable".
والأهم من ذلك ، في تحديد المدى المعجمى ، فإن المتغير مع نطاق الوظيفة له نطاق فقط داخل السياق المفاهيمي للدالة: إنه يتحرك خارج السياق عندما يتم استدعاء دالة أخرى داخل الدالة ، وينتقل إلى السياق عندما تعود الدالة - لا تدعى الدوال للمتغيرات المحلية لوظائف الاستدعاء ، والمتغيرات المحلية هي فقط في سياق داخل جسم الوظيفة التي يتم الإعلان عنها. على النقيض من ذلك ، في النطاق الديناميكي ، يمتد النطاق إلى سياق وقت التشغيل الخاص بالوظيفة: تظل المتغيرات المحلية في السياق عندما يتم استدعاء دالة أخرى ، فقط تتحرك خارج السياق عند انتهاء وظيفة التعريف ، وبالتالي تكون المتغيرات المحلية في سياق الدالة التي يتم تعريفها وجميع وظائف تسمى. في اللغات مع تحديد المدى المعنوي والوظائف المتداخلة ، تكون المتغيرات المحلية في سياق الدوال المتداخلة ، حيث أن هذه هي ضمن السياق المعجمي نفسه ، ولكن ليس في الوظائف الأخرى غير المتداخلة معًا. يُعرف المتغير المحلي لدالة تضمين كمتغير غير محلي للدالة المتداخلة. نطاق الوظيفة ينطبق أيضا على وظائف مجهولة.
def square(n):
return n * n
def sum_of_squares(n):
total = 0
i = 0
while i <= n:
total += square(i)
i += 1
return total
على سبيل المثال ، في مقتطف شفرة Python على اليمين ، يتم تعريف وظيفتين: square و sum_of_squares. مربع يحسب مربع العدد. يحسب sum_of_squares مجموع كل المربعات إلى رقم. (على سبيل المثال ، المربع (4) هو 42 = 16، و sum_of_squares (4) هو 02 + 12 + 22 + 32 + 42 = 30.)
كل من هذه الوظائف له متغير اسمه n يمثل الوسيط للدالة. هذان المتغيران n منفصلان تمامًا ولا يرتبطان ببعضهما ، على الرغم من وجودهما بنفس الاسم ، نظرًا لأنهما متغيرين نطاقًا محليًا مع نطاق وظيفي: نطاق كل منهما هو وظيفته المنفصلة ، وبالتالي لا يتداخلان. لذلك ، يمكن لـ sum_of_squares استدعاء مربع بدون تغيير n الخاص بها. وبالمثل يحتوي sum_of_squares على متغيرات باسم "i"؛ هذه المتغيرات ، نظرًا لنطاقها المحدود ، لن تتداخل مع أي متغيرات تُسمى «الإجمالي» أو "i" والتي قد تنتمي إلى أي وظيفة أخرى. وبعبارة أخرى ، لا يوجد خطر اصطدام اسم بين هذه المعرفات وأية معرّفات لا علاقة لها ، حتى لو كانت متطابقة.
لاحظ أيضًا أنه لا يتم إخفاء أي اسم: لا يوجد سوى متغير واحد اسمه n في السياق في أي وقت ، نظرًا لعدم تداخل النطاقات. على النقيض من ذلك ، كانت هناك قطعة مماثلة مكتوبة بلغة ذات نطاق ديناميكي ، فإن n في وظيفة الاستدعاء ستبقى في السياق في الدالة المطلوبة - ستتداخل النطاقات - وتكون ملثمة («مظللة») بواسطة n الجديدة في وظيفة تسمى.
نطاق الوظيفة أكثر تعقيدًا بشكل ملحوظ إذا كانت الدوال كائنات من الدرجة الأولى ويمكن إنشاؤها محليًا إلى وظيفة ثم إعادتها. في هذه الحالة ، فإن أي متغيرات في الدالة المتداخلة غير المحلية إليها (المتغيرات غير المتضمنة في تعريف الدالة ، التي تحل إلى المتغيرات في سياق مضمّن) تنشئ إغلاقًا ، ليس فقط في الوظيفة نفسها ، ولكن أيضًا بيئتها (للمتغيرات) يجب إرجاعها ، ومن المحتمل أن يتم استدعاؤها في سياق مختلف. وهذا يتطلب دعمًا كبيرًا من المترجم ، ويمكن أن يعقد تحليل البرنامج.
نطاق الملف
قاعدة تحديد نطاق خاصة بشكل خاص لـ C (و ++C) هي نطاق الملف ، حيث يكون نطاق المتغيرات والوظائف المعلن عنها في المستوى الأعلى من الملف (ليس ضمن أي وظيفة) للملف بأكمله - أو بالأحرى لـ C ، من الإعلان حتى نهاية الملف المصدر ، أو بشكل أكثر دقة وحدة الترجمة (الربط الداخلي). يمكن اعتبار ذلك شكلاً من أشكال نطاق الوحدة ، حيث يتم تحديد الوحدات النمطية بالملفات ، وفي اللغات الأكثر حداثة يتم استبدالها بنطاق وحدة صريح. نظرًا لوجود عبارات التضمين ، والتي تضيف متغيرات ووظائف للسياق الداخلي وقد تدعو نفسها أيضًا إلى تضمين عبارات ، فقد يكون من الصعب تحديد ما هو موجود في سياق الملف.
في مقتطف شفرة C أعلاه ، فإن اسم الدالة sum_of_squares له نطاق الملف.
نطاق الوحدة
في البرمجة المعيارية ، يمكن أن يكون نطاق الاسم وحدة كاملة ، ومع ذلك قد يكون منظمًا عبر ملفات متعددة. في هذا النموذج ، الوحدات هي الوحدة الأساسية لبرنامج معقد ، حيث أنها تسمح بإخفاء المعلومات وكشف واجهة محدودة. كان نطاق الوحدة رائدا في عائلة اللغات Modula ، و Python (التي تأثرت بها Modula) هو مثال معاصر تمثيلي.
في بعض لغات البرمجة الموجهة للكائنات التي تفتقر إلى الدعم المباشر للوحدات ، مثل C ++ ، يتم توفير بنية مشابهة بدلاً من التسلسل الهرمي للفئة ، حيث تكون الطبقات هي الوحدة الأساسية للبرنامج ، ويمكن أن يكون للفئة أساليب خاصة. يتم فهم ذلك بشكل صحيح في سياق الإرسال الديناميكي بدلاً من تحليل الاسم والنطاق ، على الرغم من أنهم غالباً ما يقومون بأدوار مماثلة. في بعض الحالات ، تتوفر هذه المرافق ، كما هو الحال في Python ، التي تحتوي على كل من الوحدات النمطية والفئات ، وتنظيم التعليمات البرمجية (كوظيفة على مستوى الوحدة النمطية أو طريقة خاصة تقليدية) هو اختيار للمبرمج.
النطاق العالمي
يكون للإعلان نطاق عالمي إذا كان له تأثير في جميع أنحاء البرنامج بأكمله. غالباً ما تعتبر الأسماء المتغيرة ذات النطاق العالمي - والتي تسمى المتغيرات العالمية - من الممارسات السيئة ، على الأقل في بعض اللغات ، بسبب إمكانية حدوث تضارب في الأسماء والاقتناع غير المقصود ، جنباً إلى جنب مع ضعف النمطية ، والنطاق الوظيفي أو نطاق الكتلة. ومع ذلك ، يتم استخدام النطاق العالمي عادة (اعتمادًا على اللغة) لأنواع مختلفة من المعرفات الأخرى ، مثل أسماء الدوال وأسماء الفئات وأنواع البيانات الأخرى. في هذه الحالات ، يتم استخدام آليات مثل مساحات الأسماء لتجنب التصادمات.
النطاق المعياري (الثابت) مقابل النطاق الديناميكي
إن استخدام المتغيرات المحلية - من أسماء المتغيرات ذات النطاق المحدود ، والتي لا توجد إلا داخل وظيفة محددة - يساعد على تجنب خطر اصطدام الاسم بين متغيرين مسميين متطابقين. ومع ذلك ، هناك نهجين مختلفين جدا للإجابة على هذا السؤال: ماذا يعني أن تكون «داخل» وظيفة؟
في تحديد المدى المعجمى (أو المدى المعجمى ، أو ما يسمى أيضا تحديد نطاق ثابت أو نطاق ثابت)، إذا كان نطاق اسم المتغير هو دالة معينة ، فإن نطاقه هو نص البرنامج لتعريف الوظيفة: داخل هذا النص ، يوجد اسم المتغير ، منضمًا إلى قيمة المتغير ، ولكن خارج هذا النص ، لا يوجد اسم المتغير. على النقيض من ذلك ، في النطاق الديناميكي (أو النطاق الديناميكي)، إذا كان نطاق اسم المتغير هو دالة معينة ، فإن نطاقه هو الفترة الزمنية التي يتم خلالها تنفيذ الدالة: أثناء تشغيل الدالة ، يوجد اسم المتغير ، منضمًا إلى قيمتها ، ولكن بعد إرجاع الدالة ، لا يكون اسم المتغير موجودًا. وهذا يعني أنه إذا كانت الدالة f تستدعي دالة g محددة بشكل منفصل ، فحينئذٍ تحت نطاق الفحص المعنوي ، فإن الدالة g لا تستطيع الوصول إلى المتغيرات المحلية f (بافتراض أن نص g ليس داخل نص f)، في حين تحت النطاق الديناميكي ، الدالة g يمكن الوصول إلى المتغيرات المحلية f (حيث يتم استدعاء g أثناء استدعاء f).
خذ بعين الاعتبار ، على سبيل المثال ، البرنامج على اليمين. السطر الأول ،x=1، ينشئ متغير عمومي x ويهيئه إلى x . السطر الثاني ،
function g { echo $x ; x=2 ; }، يحدد وظيفة gالتي تطبع ("echoes") القيمة الحالية لـx ، ثم تحدد x إلى 2 (الكتابة فوق القيمة السابقة). السطر الثالث function f { local x=3 ; g ; } يعرّف الدالة f التي تنشئ متغيرًا محليًا x (يخفي المتغير العام المسمى تمامًا) ويهيئه إلى 3، ثم يدعو g. السطر الرابع ، f يدعو f. السطر الخامس ، echo $x ، يطبع القيمة الحالية لـ x.
ما الذي يطبعه هذا البرنامج بالضبط؟ ذلك يعتمد على قواعد الفحص. إذا كانت لغة هذا البرنامج هي التي تستخدم الفحص المعجمي ، فإن g تطبع وتعديل المتغير العام x (لأن g معرف خارج f)، بحيث يقوم البرنامج بطباعة 1 ثم 2. على النقيض من ذلك ، إذا استخدمت هذه اللغة نطاقًا ديناميكيًا ، ثم يقوم g print بتعديل المتغير المحلي f (x) (لأنه يتم استدعاء f من داخل f)، بحيث يقوم البرنامج بطباعة 3 ثم 1. (كما يحدث ، فإن لغة البرنامج هي Bash ، والتي تستخدم النطاق الديناميكي ؛ لذا يقوم البرنامج بطباعة 3 ثم 1 .)
$ x=1
$ function g { echo $x ; x=2 ; }
$ function f { local x=3 ; g ; }
$ f # does this print 1, or 3?
3
$ echo $x # does this print 1, or 2?
1
النطاق المعجمي الثابت
مع النطاق المعجمى ، يشير الاسم دائمًا إلى البيئة المعجمية المحلية (أكثر أو أقل). هذه خاصية نص البرنامج وهي مستقلة عن مكدس الاستدعاءات وقت التشغيل بواسطة تطبيق اللغة. نظرًا لأن هذه المطابقة لا تتطلب سوى تحليل نص البرنامج الثابت ، فإن هذا النوع من الفحص يدعى أيضًا تحديد نطاق ثابت. تعتبر عملية تحديد النطاق المعياري قياسية في جميع اللغات التي تعتمد على ALGOL مثل Pascal و Modula2 و Ada بالإضافة إلى اللغات الوظيفية الحديثة مثل ML و Haskell. كما أنه يستخدم في لغة C وأقاربها النحوية والدلالية ، على الرغم من وجود أنواع مختلفة من القيود. يسمح تحديد النطاق الثابت للمبرمج بالتفكير حول مراجع الكائنات مثل المعلمات والمتغيرات والثوابت والأنواع والوظائف وما إلى ذلك كبدائل بسيطة. هذا يجعل الأمر أسهل بكثير لجعل التعليمات البرمجية النموذجية والسبب في ذلك ، حيث يمكن فهم بنية التسمية المحلية في عزلة. في المقابل ، يفرض النطاق الديناميكي على المبرمج استباق جميع السياقات الديناميكية المحتملة التي قد يتم استدعاء رمز الوحدة النمطية بها.
program A;
var I:integer;
K:char;
procedure B;
var K:real;
L:integer;
procedure C;
var M:real;
begin
(*scope A+B+C*)
end;
(*scope A+B*)
end;
(*scope A*)
end.
التاريخ
تم استخدام الفحص المعياري لـ ALGOL وتم انتقاؤه بمعظم اللغات الأخرى منذ ذلك الحين.
تم إدخال التجليد العميق ، الذي يقترب من تحديد النطاق الثابت (المعجمية) في LISP 1.5 (عبر جهاز Funarg الذي طوره ستيف رسل ، الذي كان يعمل تحت جون مكارثي). استخدم مترجم Lisp الأصلي (1960) ومعظم ليزبس مبكرًا نطاقًا ديناميكيًا ، لكن أحفاد اللغات ذات النطاق الديناميكي غالبًا ما يستخدمون نطاقًا ثابتًا ؛ لدى اللزوم والبرنامج المشترك (مع SRFI 15) نطاقًا ديناميكيًا وثابتًا. Perl هي لغة أخرى مع النطاق الديناميكي الذي أضاف نطاقًا ثابتًا بعد ذلك. ولطالما كانت اللغات مثل باسكال وC تحظى بمسح معجمي ، حيث أنهما متأثران بالأفكار التي دخلت إلى 60 ALGOL (على الرغم من أن C لم تتضمن وظائف متداخلة معًا).
يرجع مصطلح «النطاق المعجمى» إلى عام 1967 على الأقل ،
بينما يرجع مصطلح «تحديد المدى -المعجمى -الثابت» إلى عام 1970 على الأقل ، حيث تم استخدامه في مشروع MAC لوصف قواعد تحديد نطاق لهجة ldp (المعروفة آنذاك باسم " Muddle").
النطاق الديناميكي
مع النطاق الديناميكي ، يشير المعرّف العام إلى المعرف المرتبط بالبيئة الأحدث ، وهو غير شائع في اللغات الحديثة. [4] من الناحية الفنية ، يعني هذا أن كل معرّف يحتوي على كدسة عالمية للارتباطات. إدخال متغير محلي بالاسم x يدفع الربط إلى x stack العام (والذي قد يكون فارغًا)، والذي يتم إفراغه عندما يترك تدفق التحكم النطاق. يؤدي تقييم x في أي سياق دائمًا إلى الحصول على الرابط العلوي. لاحظ أن هذا لا يمكن القيام به في وقت التحويل البرمجي لأن رصة الربط موجودة فقط في وقت التشغيل ، وهذا هو السبب في أن هذا النوع من scoping يُسمى scoping الديناميكي.
بشكل عام ، يتم تعريف كتل معينة لإنشاء الارتباطات التي يكون عمرها هو وقت تنفيذ الكتلة ؛ هذا يضيف بعض ميزات الفحص الثابت إلى عملية تحديد النطاق الديناميكي. ومع ذلك ، بما أنه يمكن استدعاء جزء من الكود من العديد من المواقع والحالات المختلفة ، فقد يكون من الصعب تحديد ما هي الارتباطات التي سيتم تطبيقها عند استخدام المتغير (أو وجوده على الإطلاق) في البداية. هذا يمكن أن يكون مفيدا ؛ إن تطبيق مبدأ أقل قدر من المعرفة يشير إلى أن الشفرة تتجنب اعتمادًا على أسباب (أو ظروف) قيمة المتغير ، ولكن ببساطة استخدم القيمة وفقًا لتعريف المتغير. يمكن أن يوفر هذا التفسير الضيق للبيانات المشتركة نظامًا مرنًا للغاية لتكييف سلوك إحدى الوظائف مع الحالة (أو السياسة) الحالية للنظام. ومع ذلك ، تعتمد هذه المنفعة على توثيق دقيق لجميع المتغيرات المستخدمة بهذه الطريقة وكذلك على تجنب الحذر من الافتراضات حول سلوك المتغير ، ولا توفر أي آلية للكشف عن التداخل بين أجزاء مختلفة من البرنامج. يفحص النطاق الديناميكي أيضًا جميع مزايا الشفافية المرجعية. على هذا النحو ، يمكن أن يكون تحديد النطاق الديناميكي خطيراً وقليلًا من اللغات الحديثة تستخدمه. بعض اللغات ، مثل Perl و Common Lisp ، تسمح للمبرمج باختيار scoping ثابت أو ديناميكي عند تعريف أو إعادة تعريف متغير. تتضمن أمثلة اللغات التي تستخدم نطاقًا ديناميكيًا الشعار ، و Emacs Lisp ، و bash languages bash ، وشرطة ، و PowerShell.
عملية تحديد النطاق الديناميكي سهلة التنفيذ. للعثور على قيمة المعرف ، يمكن أن يجتاز البرنامج مكدس وقت التشغيل ، ويتحقق من كل سجل تنشيط (إطار كد لكل دالة) للحصول على قيمة للمعرف. من الناحية العملية ، يصبح هذا أكثر كفاءة من خلال استخدام قائمة الارتباطات ، وهي عبارة عن مجموعة من أزواج الاسم / القيمة. يتم دفع الأزواج إلى هذه المكدس عندما يتم إصدار الإعلانات ، ويتم بروزها عندما تخرج المتغيرات من نطاقها. الربط الضحل هو إستراتيجية بديلة أسرع بشكل كبير ، حيث تستخدم جدول مرجعي مركزي ، يربط كل اسم بمجموعته الخاصة من المعاني. هذا يتجنب البحث الخطي أثناء وقت التشغيل للعثور على اسم معين ، ولكن يجب الانتباه إلى الحفاظ على هذا الجدول بشكل صحيح. لاحظ أن كلا هذين الاستراتيجيتين يفترضان ترتيبًا أخيرًا في أول (LIFO) لترتيب ارتباطات لأي متغير واحد ؛ في الممارسة يتم ترتيب كل الارتباطات.
والتطبيق الأكثر بساطة هو تمثيل المتغيرات الديناميكية مع المتغيرات العالمية البسيطة. يتم تنفيذ الربط المحلي عن طريق حفظ القيمة الأصلية في موقع مجهول على المكدس غير المرئي للبرنامج. عندما ينتهي نطاق الربط هذا ، يتم استعادة القيمة الأصلية من هذا الموقع. في الواقع ، نشأ نطاق ديناميكي بهذه الطريقة. استخدمت التطبيقات المبكرة لـ Lisp هذه الاستراتيجية الواضحة لتنفيذ المتغيرات المحلية ، وتبقى هذه الممارسة في بعض اللهجات التي لا تزال قيد الاستخدام ، مثل GNU Emacs Lisp. تم تقديم نطاق المعجمية في Lisp في وقت لاحق. وهذا يعادل مخطط الربط الضحلة أعلاه ، فيما عدا أن الجدول المرجعي المركزي هو ببساطة بيئة الربط المتغيرة العالمية ، التي يكون فيها المعنى الحالي للمتغير هو قيمته العالمية. الحفاظ على المتغيرات العالمية ليست معقدة. على سبيل المثال ، يمكن أن يحتوي كائن الرمز على فتحة مخصصة لقيمته العمومية.
يوفر النطاق الديناميكي تجريدًا ممتازًا للتخزين المحلي لمؤشر الترابط ، ولكن إذا تم استخدامه بهذه الطريقة ، فلا يمكن أن يستند إلى حفظ واستعادة متغير عام. إستراتيجية تنفيذ محتملة هي أن يكون لكل متغير مفتاح مؤشر ترابط محلي. عند الوصول إلى المتغير ، يتم استخدام مفتاح مؤشر الترابط المحلي للوصول إلى موقع ذاكرة مؤشر الترابط المحلي (بواسطة رمز تم إنشاؤه بواسطة المحول البرمجي ، والذي يعرف المتغيرات التي تكون ديناميكية والتي تكون معجمية). إذا كان مفتاح مؤشر الترابط المحلي غير موجود لمؤشر الترابط المتصل ، فسيتم استخدام الموقع العام. عندما يكون المتغير مرتبطًا محليًا ، يتم تخزين القيمة السابقة في موقع مخفي على المكدس. يتم إنشاء تخزين مؤشر الترابط المحلي تحت مفتاح المتغير ، ويتم تخزين القيمة الجديدة هناك. مزيد من التجاوزات المتداخلة للمتغير داخل هذا الموضوع ببساطة حفظ واستعادة هذا الموقع المحلي. عندما ينتهي النطاق المبدئي .
ملاحظات
مراجع
برمجة
علم الحاسوب
مفاهيم لغات البرمجة