نطاق (برمجة الحاسوب)
في برمجة الحاسوب ، يُعرَّف نطاق ربط الاسم ( أي ارتباط اسم بكيان، مثل متغير ) بأنه الجزء من البرنامج الذي يكون فيه ربط الاسم صالحًا. بعبارة أخرى، النطاق هو المكان الذي يُمكن فيه استخدام اسم للإشارة إلى كيان . في أجزاء أخرى من البرنامج، قد يُشير الاسم إلى كيان مختلف (قد يكون له ربط مختلف)، أو لا يُشير إلى أي شيء على الإطلاق (قد يكون غير مرتبط). يُساعد النطاق على منع تضارب الأسماء من خلال السماح للاسم نفسه بالإشارة إلى كائنات مختلفة، طالما أن لكل اسم نطاقه الخاص. يُعرف نطاق ربط الاسم أيضًا برؤية الكيان ، خاصةً في المراجع القديمة أو التقنية، وهذا يتعلق بالكيان المُشار إليه، وليس بالاسم المُشير.
يُستخدم مصطلح "النطاق" أيضًا للإشارة إلى مجموعة جميع روابط الأسماء الصالحة ضمن جزء من البرنامج أو عند نقطة معينة فيه، وهو ما يُشار إليه بشكل أدق باسم السياق أو البيئة . [ أ ]
بالمعنى الدقيق [ ب ] وفي الواقع العملي لمعظم لغات البرمجة ، يُشير مصطلح "جزء من البرنامج" إلى جزء من شفرة المصدر (مساحة نصية)، ويُعرف بالنطاق المعجمي . أما في بعض اللغات، فيُشير "جزء من البرنامج" إلى جزء من وقت التشغيل (فترة التنفيذ )، ويُعرف بالنطاق الديناميكي . كلا المصطلحين مُضلِّلان إلى حد ما - إذ يُسيئان استخدام المصطلحات التقنية، كما هو مُوضَّح في التعريف - لكن التمييز بينهما دقيق وواضح، وهما المصطلحان القياسيان المُستخدمان. يُركِّز هذا المقال بشكل أساسي على النطاق المعجمي، مع فهم النطاق الديناميكي من خلال مُقارنته بالنطاق المعجمي.
في معظم الحالات، يُعدّ حلّ الأسماء بناءً على النطاق المعجمي سهل الاستخدام والتنفيذ نسبيًا، إذ يُمكن أثناء الاستخدام قراءة الكود المصدري عكسيًا لتحديد الكيان الذي يُشير إليه الاسم، كما يُمكن أثناء التنفيذ الاحتفاظ بقائمة من الأسماء والسياقات عند تجميع البرنامج أو تفسيره . تظهر الصعوبات في إخفاء الأسماء ، والتصريحات المسبقة ، ورفع المتغيرات ، بينما تظهر صعوبات أكثر دقة مع المتغيرات غير المحلية ، لا سيما في الدوال المغلقة .
تعريف
التعريف الدقيق لنطاق الاسم (المعرّف) ( المُعَرِّف ) واضح لا لبس فيه: النطاق المُعَرِّف هو "جزء من شفرة المصدر الذي ينطبق عليه ربط الاسم بكيان". لم يطرأ أي تغيير يُذكر على هذا التعريف منذ عام 1960 في مواصفات لغة ALGOL 60. فيما يلي نماذج من مواصفات اللغة:
- ALGOL 60 (1960) [ 1 ]
- تُصنّف الكميات إلى الأنواع التالية: المتغيرات البسيطة، والمصفوفات، والتسميات، والعبارات المفتاحية، والإجراءات. ويُعرّف نطاق الكمية بأنه مجموعة العبارات والتعبيرات التي يكون فيها تعريف المعرّف المرتبط بتلك الكمية صحيحًا.
- ج (2007) [ 2 ]
- يمكن أن يُشير المُعرِّف إلى كائن، أو دالة، أو وسم، أو عنصر من بنية، أو اتحاد، أو تعداد، أو اسم تعريف نوع ، أو اسم تسمية، أو اسم ماكرو، أو مُعامل ماكرو. ويمكن للمُعرِّف نفسه أن يُشير إلى كيانات مختلفة في نقاط مختلفة من البرنامج. [...] بالنسبة لكل كيان مختلف يُشير إليه المُعرِّف، يكون هذا المُعرِّف مرئيًا (أي قابلًا للاستخدام) فقط ضمن نطاق نص البرنامج .
- Go (2013) [ 3 ]
- يربط التصريح مُعرّفًا غير فارغ بثابت أو نوع أو متغير أو دالة أو تسمية أو حزمة. [...] نطاق المُعرّف المُصرّح به هو مدى نص المصدر الذي يُشير فيه المُعرّف إلى الثابت أو النوع أو المتغير أو الدالة أو التسمية أو الحزمة المُحددة.
يشير مصطلح "النطاق" في أغلب الأحيان إلى متى يمكن لاسم معين أن يشير إلى متغير معين - عندما يكون للتصريح تأثير - ولكن يمكن أن ينطبق أيضًا على كيانات أخرى، مثل الدوال والأنواع والفئات والتسميات والثوابت والتعدادات.
النطاق المعجمي مقابل النطاق الديناميكي
يكمن أحد الفروق الأساسية في النطاق في معنى "جزء من البرنامج". في اللغات ذات النطاق المعجمي (ويُسمى أيضًا النطاق الثابت )، يعتمد حلّ الاسم على موقعه في شفرة المصدر والسياق المعجمي (ويُسمى أيضًا السياق الثابت )، والذي يُحدد بمكان تعريف المتغير أو الدالة المُسماة. في المقابل، في اللغات ذات النطاق الديناميكي، يعتمد حلّ الاسم على حالة البرنامج عند مصادفة الاسم، والتي يُحددها سياق التنفيذ (ويُسمى أيضًا سياق وقت التشغيل ، أو سياق الاستدعاء ، أو السياق الديناميكي ). عمليًا، في النطاق المعجمي، يُحلّ الاسم بالبحث في السياق المعجمي المحلي، ثم إذا فشل ذلك، بالبحث في السياق المعجمي الخارجي، وهكذا؛ بينما في النطاق الديناميكي، يُحلّ الاسم بالبحث في سياق التنفيذ المحلي، ثم إذا فشل ذلك، بالبحث في سياق التنفيذ الخارجي، وهكذا، بالتقدم صعودًا في مكدس الاستدعاءات. [ 4 ]
تستخدم معظم لغات البرمجة الحديثة النطاق المعجمي للمتغيرات والدوال، بينما يُستخدم النطاق الديناميكي في بعض اللغات، ولا سيما بعض لهجات لغة ليسب، وبعض لغات البرمجة النصية، وبعض لغات القوالب . [ ج ] توفر لغة بيرل 5 كلا النطاقين المعجمي والديناميكي. تُعرف الدوال التي تستخدم متغيرات ذات نطاق معجمي باسم الدوال المغلقة .
يمكن تحديد الحل المعجمي في وقت الترجمة ، ويُعرف أيضًا باسم الربط المبكر ، بينما لا يمكن تحديد الحل الديناميكي بشكل عام إلا في وقت التشغيل ، وبالتالي يُعرف باسم الربط المتأخر .
مفاهيم ذات صلة
في البرمجة كائنية التوجه ، يختار الإرسال الديناميكي دالة الكائن أثناء التشغيل، مع العلم أن تحديد ما إذا كان ربط الاسم الفعلي يتم أثناء الترجمة أو أثناء التشغيل يعتمد على لغة البرمجة. ويُعدّ النطاق الديناميكي الفعلي شائعًا في لغات الماكرو ، التي لا تُجري تحليل الأسماء مباشرةً، بل تتوسع في مكانها.
تستخدم بعض أطر البرمجة، مثل AngularJS، مصطلح "النطاق" من الناحية التركيبية بمعنى مختلف تمامًا عن معناه في هذه المقالة. ففي هذه الأطر، يُعد النطاق مجرد كائن من لغة البرمجة المستخدمة ( JavaScript في حالة AngularJS)، ويُستخدم بطرق معينة لمحاكاة النطاق الديناميكي في لغة تعتمد على النطاق المعجمي لمتغيراتها. ويمكن أن تكون نطاقات AngularJS هذه ضمن السياق أو خارجه (بالمعنى المعتاد للمصطلح) في أي جزء من البرنامج، مع اتباع قواعد نطاق المتغيرات المعتادة في اللغة كأي كائن آخر، واستخدام قواعد التوريث والتضمين الخاصة بها. وفي سياق AngularJS، يُستخدم أحيانًا مصطلح "$scope" (مع علامة الدولار) لتجنب الالتباس ، ولكن استخدام علامة الدولار في أسماء المتغيرات غالبًا ما يُنصح بتجنبه وفقًا لأدلة الأسلوب. [ 5 ]
يستخدم
يُعدّ النطاق عنصرًا هامًا في عملية تحديد الأسماء ، [ ] وهو بدوره أساسي لدلالات اللغة . يختلف تحديد الأسماء (بما في ذلك النطاق) بين لغات البرمجة، وداخل لغة البرمجة الواحدة، يختلف باختلاف نوع الكيان؛ وتُسمى قواعد النطاق بقواعد النطاق (أو قواعد تحديد النطاق ). إلى جانب مساحات الأسماء ، تُعدّ قواعد النطاق بالغة الأهمية في البرمجة المعيارية ، بحيث لا يؤدي تغيير جزء من البرنامج إلى تعطيل جزء آخر غير ذي صلة.
ملخص
عند مناقشة النطاق، توجد ثلاثة مفاهيم أساسية: النطاق، والمدى، والسياق . غالبًا ما يُخلط بين "النطاق" و"السياق": فالنطاق خاصيةٌ لربط اسم، بينما السياق خاصيةٌ لجزء من البرنامج، سواءً كان جزءًا من شفرة المصدر ( السياق المعجمي أو السياق الثابت ) أو جزءًا من وقت التشغيل ( سياق التنفيذ، أو سياق وقت التشغيل، أو سياق الاستدعاء ، أو السياق الديناميكي ). يتكون سياق التنفيذ من السياق المعجمي (عند نقطة التنفيذ الحالية) بالإضافة إلى حالة وقت التشغيل الإضافية مثل مكدس الاستدعاءات . [ هـ ] من الناحية الدقيقة، أثناء التنفيذ، يدخل البرنامج ويخرج من نطاقات روابط الأسماء المختلفة، وفي نقطة معينة من التنفيذ، تكون روابط الأسماء "داخل السياق" أو "خارج السياق"، وبالتالي "تدخل روابط الأسماء في السياق" أو "تخرج من السياق" عندما يدخل تنفيذ البرنامج النطاق أو يخرج منه. [ و ] مع ذلك، في الممارسة العملية، يكون الاستخدام أكثر مرونة.
يُعدّ النطاق مفهومًا على مستوى شفرة المصدر، وخاصيةً لربط الأسماء، لا سيما ربط أسماء المتغيرات أو الدوال - حيث تُشير الأسماء في شفرة المصدر إلى كيانات في البرنامج - وهو جزء من سلوك مُصرّف أو مُفسّر اللغة. ولذلك، تُشبه مسائل النطاق مسألة المؤشرات ، وهي نوع من المراجع يُستخدم في البرامج بشكل عام. يُشبه استخدام قيمة متغير عندما يكون اسمه ضمن السياق ولكن المتغير غير مُهيّأ، الوصول إلى قيمة مؤشر غير مُعرّف ( غير مُعرّف) . ومع ذلك، بما أن المتغيرات لا تُحذف إلا عند خروجها من السياق، فلا يوجد ما يُشابه المؤشر المُعلّق .
بالنسبة للكيانات مثل المتغيرات، يُعد النطاق جزءًا من دورة الحياة (المعروفة أيضًا بالمدى ) - إذ لا يمكن للاسم أن يشير إلا إلى متغير موجود (ربما بقيمة غير مُعرَّفة)، ولكن المتغيرات الموجودة ليست بالضرورة مرئية: فقد يكون المتغير موجودًا ولكنه غير قابل للوصول (يتم تخزين القيمة ولكن لا يُشار إليها ضمن سياق مُحدد)، أو يمكن الوصول إليه ولكن ليس عبر الاسم المُعطى، وفي هذه الحالة يكون خارج السياق (يكون البرنامج "خارج نطاق الاسم"). في حالات أخرى، تكون "دورة الحياة" غير ذات صلة - فمثلاً، يكون للتسمية (موقع مُسمى في شفرة المصدر) دورة حياة مُطابقة لدورة حياة البرنامج (بالنسبة للغات المُترجمة بشكل ثابت)، ولكن قد تكون ضمن السياق أو لا في نقطة مُعينة من البرنامج، وكذلك بالنسبة للمتغيرات الثابتة - يكون المتغير العام الثابت ضمن سياق البرنامج بأكمله، بينما يكون المتغير المحلي الثابت ضمن سياق دالة أو سياق محلي آخر فقط، ولكن كلاهما له دورة حياة تشغيل البرنامج بالكامل.
تُعرف عملية تحديد الكيان الذي يشير إليه اسم ما بحلّ الأسماء أو ربط الأسماء (خاصةً في البرمجة كائنية التوجه )، وتختلف هذه العملية بين لغات البرمجة. عند إعطاء اسم، تتحقق اللغة (أو بالأحرى المترجم أو المفسر) من جميع الكيانات الموجودة في السياق بحثًا عن تطابقات؛ وفي حالة وجود غموض (كيانان يحملان الاسم نفسه، مثل متغير عام ومتغير محلي يحملان الاسم نفسه)، تُستخدم قواعد حلّ الأسماء للتمييز بينهما. في أغلب الأحيان، يعتمد حلّ الأسماء على قاعدة "من السياق الداخلي إلى السياق الخارجي"، مثل قاعدة LEGB في بايثون (محلي، محيط، عام، مدمج): حيث تُحلّل الأسماء ضمنيًا إلى أضيق سياق ذي صلة. في بعض الحالات، يمكن تحديد حلّ الأسماء بشكل صريح، كما هو الحال باستخدام الكلمتين المفتاحيتين `name` globalو` nonlocalname` في بايثون؛ وفي حالات أخرى، لا يمكن تجاوز القواعد الافتراضية.
عندما يُستخدم اسمان متطابقان في سياق واحد، ويشيران إلى كيانين مختلفين، يُقال إن عملية إخفاء الأسماء تحدث، حيث يُخفي الاسم ذو الأولوية الأعلى (عادةً ما يكون الأقرب) الاسم ذو الأولوية الأقل. على مستوى المتغيرات، يُعرف هذا باسم تظليل المتغيرات . ونظرًا لاحتمالية حدوث أخطاء منطقية نتيجةً للإخفاء، فإن بعض لغات البرمجة تمنع أو تُثني عن استخدامه، وتُصدر خطأً أو تحذيرًا أثناء الترجمة أو التشغيل.
تختلف قواعد نطاق المتغيرات في لغات البرمجة المختلفة باختلاف أنواع التصريحات والأسماء. وتؤثر هذه القواعد بشكل كبير على دلالات اللغة ، وبالتالي على سلوك البرامج وصحتها. ففي لغات مثل C++ ، لا يحمل الوصول إلى متغير غير مُعرَّف دلالات واضحة، وقد يؤدي إلى سلوك غير مُعرَّف ، كما هو الحال عند الإشارة إلى مؤشر معلق ؛ أما التصريحات أو الأسماء المستخدمة خارج نطاقها فتُنتج أخطاءً في بناء الجملة .
غالباً ما ترتبط النطاقات ببنى لغوية أخرى ويتم تحديدها ضمنياً، ولكن العديد من اللغات تقدم أيضاً بنى خاصة للتحكم في النطاق.
مستويات النطاق
يتراوح نطاق المتغيرات من تعبير واحد إلى البرنامج بأكمله، مع وجود العديد من التدرجات الممكنة بينهما. أبسط قواعد النطاق هي النطاق العام، حيث تكون جميع الكيانات مرئية في جميع أنحاء البرنامج. أما أبسط قواعد النطاق المعياري فهي النطاق ثنائي المستوى، حيث يكون النطاق العام متاحًا في أي مكان في البرنامج، بينما يكون النطاق المحلي متاحًا داخل الدالة. تسمح البرمجة المعيارية الأكثر تطورًا بنطاق وحدة منفصل، حيث تكون الأسماء مرئية داخل الوحدة (خاصة بالوحدة) ولكنها غير مرئية خارجها. داخل الدالة، تسمح بعض اللغات، مثل لغة C، بنطاق الكتلة لتقييد النطاق إلى مجموعة فرعية من الدالة؛ بينما تسمح لغات أخرى، ولا سيما لغات البرمجة الوظيفية ، بنطاق التعبير لتقييد النطاق إلى تعبير واحد. تشمل النطاقات الأخرى نطاق الملف (خاصة في لغة C) الذي يعمل بشكل مشابه لنطاق الوحدة، ونطاق الكتلة خارج الدوال (خاصة في لغة Perl).
تكمن إحدى المسائل الدقيقة في تحديد بداية ونهاية نطاق المتغير. ففي بعض اللغات، مثل لغة C، يبدأ نطاق الاسم من لحظة تعريفه، وبالتالي قد يكون للأسماء المختلفة المُعرَّفة ضمن كتلة برمجية معينة نطاقات مختلفة. يتطلب هذا تعريف الدوال قبل استخدامها، وإن لم يكن بالضرورة تعريفها، ويتطلب تعريفًا مسبقًا في بعض الحالات، لا سيما في حالة الاستدعاء الذاتي المتبادل. أما في لغات أخرى، مثل بايثون، فيبدأ نطاق الاسم من بداية الكتلة البرمجية ذات الصلة حيث تم تعريف الاسم (مثل بداية دالة)، بغض النظر عن مكان تعريفه، لذا فإن جميع الأسماء ضمن كتلة برمجية معينة لها نفس النطاق. في جافا سكريبت، يبدأ نطاق الاسم المُعرَّف باستخدام `input` letأو const`input` من لحظة تعريفه، بينما varيبدأ نطاق الاسم المُعرَّف باستخدام `input` من بداية الدالة التي تم تعريف الاسم فيها، وهو ما يُعرف برفع المتغيرات . يختلف سلوك الأسماء في السياق التي لها قيمة غير مُعرَّفة: ففي بايثون، يؤدي استخدام أسماء غير مُعرَّفة إلى خطأ وقت التشغيل، بينما في جافا سكريبت، يمكن استخدام الأسماء غير المُعرَّفة المُعرَّفة باستخدام `input` varفي جميع أنحاء الدالة لأنها مرتبطة ضمنيًا بالقيمة `undefined` undefined.
نطاق التعبير
نطاق ربط الاسم هو تعبير ، يُعرف بنطاق التعبير . يتوفر نطاق التعبير في العديد من اللغات، وخاصة اللغات الوظيفية التي توفر ميزة تُسمى تعبيرات let، والتي تسمح بأن يكون نطاق التصريح تعبيرًا واحدًا. يُعد هذا مفيدًا، على سبيل المثال، إذا كانت هناك حاجة إلى قيمة وسيطة لإجراء عملية حسابية. على سبيل المثال، في لغة Standard ML ، إذا كانت f()القيمة المُعادة 12هي ، فإن هو تعبير يُقيّم إلى ، باستخدام متغير مؤقت يُسمى لتجنب استدعاء مرتين. تُقارب بعض اللغات ذات نطاق الكتلة هذه الوظيفة من خلال توفير صيغة لتضمين كتلة في تعبير؛ على سبيل المثال، يمكن كتابة تعبير Standard ML المذكور أعلاه في لغة Perl على النحو التالي: ، أو في لغة GNU C على النحو التالي: .let val x = f() in x * x end144xf()do{my$x=f();$x*$x}({intx=f();x*x;})
في لغة بايثون، يكون للمتغيرات المساعدة في تعبيرات المولدات وفهم القوائم (في بايثون 3) نطاق تعبير.
في لغة C، تتمتع أسماء المتغيرات في نموذج الدالة بنطاق تعبيري، يُعرف في هذا السياق بنطاق بروتوكول الدالة . ولأن أسماء المتغيرات في النموذج لا يُشار إليها (قد تختلف في التعريف الفعلي) - فهي مجرد أسماء وهمية - فغالبًا ما تُحذف، مع أنها قد تُستخدم لإنشاء التوثيق، على سبيل المثال.
نطاق الكتلة
نطاق ربط الاسم هو كتلة ، تُعرف بنطاق الكتلة . يتوفر نطاق الكتلة في العديد من لغات البرمجة ذات البنية الكتلية، ولكن ليس جميعها. بدأ هذا مع لغة ALGOL 60 ، حيث "كل تصريح ... صالح فقط لتلك الكتلة."، [ 6 ] ويرتبط اليوم بشكل خاص بلغات عائلتي باسكال وسي . غالبًا ما تكون هذه الكتلة موجودة داخل دالة، مما يقيد النطاق بجزء من تلك الدالة، ولكن في بعض الحالات، مثل لغة بيرل، قد لا تكون الكتلة داخل دالة.
دالة مجموع_المربعات ( عدد صحيح م ) { عدد صحيح النتيجة = 0 ؛ من أجل ( عدد صحيح ن = 1 ؛ ن <= م ؛ ++ ن ) { عدد صحيح ن_تربيع = ن * ن ؛ النتيجة += ن_تربيع ؛ } إرجاع النتيجة ؛ }يُعدّ كود C الموضح هنا مثالًا نموذجيًا على استخدام نطاق الكتلة، حيث تم تحديد نطاق متغيرين ضمن الحلقة: متغير الحلقة n ، الذي تتم تهيئته مرة واحدة وزيادته في كل تكرار للحلقة، والمتغير المساعد n_squared ، الذي تتم تهيئته في كل تكرار. والهدف من ذلك هو تجنب إضافة متغيرات إلى نطاق الدالة تكون ذات صلة بكتلة معينة فقط؛ فعلى سبيل المثال، يمنع هذا حدوث أخطاء ناتجة عن تعيين قيمة خاطئة لمتغير الحلقة العام i . في هذا المثال، لا يتم عادةً إسناد التعبير n * nإلى متغير مساعد، ويتم كتابة جسم الحلقة كما هو، result += n * nولكن في الأمثلة الأكثر تعقيدًا، تكون المتغيرات المساعدة مفيدة.
تُستخدم الكتل البرمجية بشكل أساسي للتحكم في تدفق البرنامج، كما هو الحال مع حلقات if و while و for، وفي هذه الحالات، يعني نطاق الكتلة أن نطاق المتغير يعتمد على بنية تدفق تنفيذ الدالة. مع ذلك، تسمح اللغات التي تدعم نطاق الكتلة عادةً باستخدام الكتل "المجردة"، والتي يقتصر غرضها على توفير تحكم دقيق في نطاق المتغير. على سبيل المثال، يمكن تعريف متغير مساعد داخل كتلة برمجية، ثم استخدامه (مثلاً، إضافته إلى متغير بنطاق دالة) ثم حذفه عند انتهاء الكتلة، أو يمكن تضمين حلقة while داخل كتلة برمجية تُهيئ المتغيرات المستخدمة داخل الحلقة والتي يجب تهيئتها مرة واحدة فقط.
من الخصائص الدقيقة لبعض لغات البرمجة، مثل Algol 68 وC (كما هو موضح في هذا المثال، والمُعتمدة منذ C99 )، إمكانية تعريف متغيرات نطاق الكتلة ليس فقط داخل جسم الكتلة، بل أيضًا داخل عبارة التحكم، إن وُجدت. وهذا يُشابه مُعاملات الدوال، التي تُعرّف في تعريف الدالة (قبل بدء كتلة جسم الدالة)، وتكون ضمن نطاق جسم الدالة بأكمله. يُستخدم هذا بشكل أساسي في حلقات for ، التي تحتوي على عبارة تهيئة منفصلة عن شرط الحلقة، على عكس حلقات while، وهو أسلوب شائع.
يمكن استخدام نطاق الكتلة للتظليل. في هذا المثال، كان من الممكن تسمية المتغير المساعد داخل الكتلة بـ n ، مما يُخفي اسم المعامل، لكن هذا يُعتبر أسلوبًا غير مُستحب نظرًا لاحتمالية حدوث أخطاء. علاوة على ذلك، فإن بعض اللغات المُشتقة من لغة C، مثل Java وC#، على الرغم من دعمها لنطاق الكتلة (حيث يُمكن إخراج متغير محلي من سياقه قبل نهاية الدالة)، لا تسمح لمتغير محلي بإخفاء آخر. في هذه اللغات، ستؤدي محاولة تعريف المتغير n الثاني إلى خطأ في بناء الجملة، وسيتعين إعادة تسمية أحد المتغيرين n .
إذا استُخدمت كتلة برمجية لتعيين قيمة متغير، فإن نطاق الكتلة يتطلب تعريف المتغير خارج الكتلة. وهذا يُعقّد استخدام العبارات الشرطية ذات التعيين الفردي . على سبيل المثال، في لغة بايثون، التي لا تستخدم نطاق الكتلة، يمكن تهيئة متغير على النحو التالي:
إذا كان c : a = "foo" وإلا : a = ""حيث aيمكن الوصول إليه بعد ifالبيان.
في لغة بيرل، التي تتميز بنطاق الكتلة، يتطلب هذا بدلاً من ذلك تعريف المتغير قبل الكتلة:
my $a ; if ( c ) { $a = 'foo' ; } else { $a = '' ; }غالبًا ما يُعاد كتابة هذا باستخدام التعيين المتعدد، مع تهيئة المتغير بقيمة افتراضية. في بايثون (حيث لا يكون ذلك ضروريًا) سيكون الأمر كالتالي:
a = "" إذا كان c : a = "foo"بينما في لغة بيرل سيكون هذا كالتالي:
my $a = '' ; if ( c ) { $a = 'foo' ; }في حالة تعيين متغير واحد، يكون البديل هو استخدام عامل التشغيل الثلاثي لتجنب الكتلة، ولكن هذا ليس ممكنًا بشكل عام لتعيينات المتغيرات المتعددة، ويصعب قراءته للمنطق المعقد.
هذه مشكلة أكثر أهمية في لغة C، لا سيما فيما يتعلق بتعيين السلاسل النصية، حيث يمكن لتهيئة السلسلة النصية تخصيص الذاكرة تلقائيًا، بينما يتطلب تعيين السلسلة النصية لمتغير مهيأ بالفعل تخصيص الذاكرة ونسخ السلسلة النصية والتحقق من نجاح هذه العمليات.
{ my $counter = 0 ; sub increment_counter { return ++ $counter ; } }تسمح بعض لغات البرمجة بتطبيق مفهوم نطاق الكتلة، بدرجات متفاوتة، خارج نطاق الدالة. على سبيل المثال، في مقتطف بيرل على اليمين، $counterيُمثل `a` اسم متغير بنطاق كتلة (بسبب استخدام الكلمة myالمفتاحية `{{}}}`)، بينما increment_counterيُمثل `a` اسم دالة بنطاق عام. كل استدعاء لـ `a` increment_counterسيزيد قيمة `a` $counterبمقدار واحد، ويعيد القيمة الجديدة. يمكن للكود خارج هذه الكتلة استدعاء `a` increment_counter، لكن لا يمكنه الحصول على قيمة `a` أو تغييرها بأي طريقة أخرى $counter. يُتيح هذا الأسلوب تعريف الدوال المغلقة في بيرل.
نطاق الوظيفة
عندما لا يتجاوز نطاق المتغيرات المُعلنة داخل دالة نطاق تلك الدالة، يُعرف ذلك بنطاق الدالة . [ 7 ] يتوفر نطاق الدالة في معظم لغات البرمجة التي تُتيح إنشاء متغير محلي داخل دالة أو إجراء فرعي : وهو متغير ينتهي نطاقه (أي يخرج من سياقه) عند انتهاء الدالة. في معظم الحالات، يكون عمر المتغير هو مدة استدعاء الدالة - فهو متغير تلقائي ، يُنشأ عند بدء الدالة (أو عند تعريف المتغير)، ويُحذف عند انتهاء الدالة - بينما يكون نطاق المتغير داخل الدالة، مع العلم أن معنى "داخل" يعتمد على ما إذا كان النطاق معجميًا أم ديناميكيًا. مع ذلك، تُتيح بعض اللغات، مثل لغة C، أيضًا المتغيرات المحلية الثابتة ، حيث يكون عمر المتغير هو عمر البرنامج بالكامل، ولكن يكون المتغير ضمن سياق الدالة فقط. في حالة المتغيرات المحلية الثابتة، يُنشأ المتغير عند بدء تشغيل البرنامج، ويُحذف فقط عند انتهائه، كما هو الحال مع المتغير العام الثابت ، ولكنه يكون ضمن سياق الدالة فقط، مثل المتغير المحلي التلقائي.
من المهم ملاحظة أنه في النطاق المعجمي، يكون نطاق المتغير ذي النطاق الوظيفي محصورًا ضمن السياق المعجمي للدالة فقط؛ إذ يخرج من هذا السياق عند استدعاء دالة أخرى داخل الدالة، ويعود إليه عند انتهاء الدالة. لا تستطيع الدوال المستدعاة الوصول إلى المتغيرات المحلية للدوال المستدعِية، وتكون المتغيرات المحلية ضمن سياق جسم الدالة التي تم تعريفها فيها فقط. على النقيض من ذلك، في النطاق الديناميكي، يمتد النطاق إلى سياق تنفيذ الدالة: تبقى المتغيرات المحلية ضمن سياقها عند استدعاء دالة أخرى، ولا تخرج منه إلا عند انتهاء الدالة المُعرِّفة، وبالتالي تكون المتغيرات المحلية ضمن سياق الدالة التي تم تعريفها فيها وجميع الدوال المستدعاة . في اللغات التي تستخدم النطاق المعجمي والدوال المتداخلة ، تكون المتغيرات المحلية ضمن سياق الدوال المتداخلة، لأنها تقع ضمن نفس السياق المعجمي، ولكن ليس ضمن سياق الدوال الأخرى غير المتداخلة معجميًا. يُعرف المتغير المحلي للدالة المُحيطة بالمتغير غير المحلي للدالة المتداخلة. ينطبق نطاق الدالة أيضًا على الدوال المجهولة .
دالة مربعة ( ن : عدد صحيح ) -> عدد صحيح : تُرجع ن * ندالة مجموع_المربعات ( ن : عدد صحيح ) -> عدد صحيح : المجموع : عدد صحيح = 0، i : عدد صحيح = 0، بينما i <= ن : المجموع += مربع ( i )، i += 1، إرجاع المجموععلى سبيل المثال، في مقتطف كود بايثون على اليمين، تم تعريف دالتين: الأولى squareلحساب مربع عدد، sum_of_squaresوالثانية لحساب مجموع مربعات الأعداد حتى ذلك العدد. (على سبيل المثال ، مربع العدد يساوي 4² ، ومربع العدد يساوي 0² + 1² + 2² + 3² + 4² ) .squaresum_of_squaressquare(4) 16sum_of_squares(4) 30
تحتوي كل دالة من هذه الدوال على متغير يُسمى n يُمثل وسيط الدالة. هذان المتغيران n منفصلان تمامًا وغير مرتبطين، على الرغم من تشابه اسميهما، لأنهما متغيران محليان ضمن نطاق الدالة: نطاق كل منهما دالة مستقلة بذاتها، وبالتالي لا يتداخلان. لذلك، sum_of_squaresيمكن استدعاء الدالة دون تغيير قيمة nsquare الخاصة بها . وبالمثل، تحتوي الدالة على متغيرين يُسميان total و i ؛ هذان المتغيران، نظرًا لنطاقهما المحدود، لن يتعارضا مع أي متغيرات أخرى تحمل الاسم total أو i قد تنتمي إلى أي دالة أخرى. بعبارة أخرى، لا يوجد خطر من حدوث تضارب في الأسماء بين هذه الأسماء وأي أسماء أخرى غير مرتبطة بها، حتى لو كانت متطابقة.sum_of_squares
لا يحدث إخفاء للأسماء: يوجد متغير واحد فقط باسم n في السياق في أي وقت، حيث لا تتداخل النطاقات. على النقيض من ذلك، إذا كُتبت فقرة مماثلة بلغة ذات نطاق ديناميكي، فإن n في الدالة المُستدعِية سيبقى في سياق الدالة المُستدعاة - ستتداخل النطاقات - وسيتم إخفاؤه (تظليله) بواسطة n الجديد في الدالة المُستدعاة.
يصبح نطاق الدالة أكثر تعقيدًا بشكل ملحوظ إذا كانت الدوال كائنات من الدرجة الأولى، ويمكن إنشاؤها محليًا بواسطة دالة أخرى ثم إعادتها. في هذه الحالة، تُنشئ أي متغيرات في الدالة المتداخلة غير محلية لها (المتغيرات غير المُرتبطة في تعريف الدالة، والتي تُحل إلى متغيرات في سياق مُحيط) إغلاقًا ، حيث لا يجب إعادة الدالة نفسها فحسب، بل يجب أيضًا إعادة سياقها (من المتغيرات)، ثم استدعاؤها لاحقًا في سياق مختلف. يتطلب هذا دعمًا أكبر بكثير من المُصرّف، وقد يُعقّد تحليل البرنامج .
نطاق الملف
نطاق ربط الاسم هو ملف، ويُعرف هذا بنطاق الملف . يُعد نطاق الملف خاصًا بلغة C (وC++) إلى حد كبير، حيث يمتد نطاق المتغيرات والدوال المُعلنة في المستوى الأعلى من الملف (وليس داخل أي دالة) ليشمل الملف بأكمله، أو بالأحرى، في لغة C، من لحظة الإعلان حتى نهاية ملف المصدر، أو تحديدًا وحدة الترجمة (الربط الداخلي). يُمكن اعتبار هذا شكلًا من أشكال نطاق الوحدة، حيث تُعرّف الوحدات بالملفات، وفي اللغات الحديثة يُستبدل بنطاق وحدة صريح. نظرًا لوجود عبارات التضمين، التي تُضيف متغيرات ودوال إلى السياق الداخلي، وقد تستدعي بدورها عبارات تضمين أخرى، قد يصعب تحديد ما هو موجود في سياق الملف.
في مقتطف كود C أعلاه، اسم الدالة sum_of_squaresله نطاق عام (في لغة C، يُسمى ارتباطًا خارجيًا). إضافة staticإلى توقيع الدالة سيؤدي إلى نطاق ملف (ارتباط داخلي).
نطاق الوحدة
نطاق ربط الاسم هو وحدة نمطية، ويُعرف هذا بنطاق الوحدة النمطية . يتوفر نطاق الوحدة النمطية في لغات البرمجة النمطية حيث تُشكّل الوحدات النمطية (التي قد تمتد عبر ملفات متعددة) الوحدة الأساسية للبرنامج المعقد، إذ تسمح بإخفاء المعلومات وعرض واجهة محدودة. وقد رُوّج لنطاق الوحدة النمطية في عائلة لغات Modula ، وتُعدّ لغة Python (التي تأثرت بـ Modula) مثالًا معاصرًا بارزًا.
في بعض لغات البرمجة كائنية التوجه التي تفتقر إلى دعم مباشر للوحدات، مثل لغة C++ قبل إصدار C++20 ، [ 8 ] يُوفر هيكل الفئات بنيةً مشابهة، حيث تُمثل الفئات الوحدة الأساسية للبرنامج، ويمكن أن تحتوي الفئة على توابع خاصة. يُفهم هذا بشكل صحيح في سياق التوزيع الديناميكي وليس في سياق تحديد الأسماء والنطاق، على الرغم من أنهما غالبًا ما يؤديان أدوارًا متشابهة. في بعض الحالات، تتوفر كلتا الميزتين، كما هو الحال في لغة بايثون، التي تدعم كلاً من الوحدات والفئات، ويكون تنظيم الكود (كدالة على مستوى الوحدة أو كدالة خاصة تقليدية) خيارًا للمبرمج.
نطاق عالمي
يُعرَّف نطاق ربط الاسم بأنه البرنامج بأكمله، ويُسمى النطاق العام . غالبًا ما تُعتبر أسماء المتغيرات ذات النطاق العام - والتي تُسمى المتغيرات العامة - ممارسةً سيئة، على الأقل في بعض لغات البرمجة، نظرًا لاحتمالية حدوث تضارب في الأسماء وإخفاء غير مقصود، فضلًا عن ضعف قابلية التجزئة، ويُفضَّل استخدام نطاق الدالة أو نطاق الكتلة. مع ذلك، يُستخدم النطاق العام عادةً (بحسب لغة البرمجة) لأنواع أخرى من الأسماء، مثل أسماء الدوال، وأسماء الأصناف ، وأسماء أنواع البيانات الأخرى . في هذه الحالات، تُستخدم آليات مثل مساحات الأسماء لتجنب التضارب.
النطاق المعجمي مقابل النطاق الديناميكي
يساعد استخدام المتغيرات المحلية - وهي أسماء متغيرات ذات نطاق محدود، لا توجد إلا داخل دالة محددة - على تجنب خطر تضارب الأسماء بين متغيرين يحملان نفس الاسم. ومع ذلك، هناك منهجان مختلفان تمامًا للإجابة على هذا السؤال: ما معنى أن يكون المتغير "داخل" دالة؟
في النطاق المعجمي (أو النطاق المعجمي ؛ ويُسمى أيضًا النطاق الثابت )، إذا كان نطاق اسم متغير هو دالة معينة، فإن نطاقه هو نص تعريف الدالة: ضمن هذا النص، يكون اسم المتغير موجودًا ومرتبطًا بقيمته، ولكن خارج هذا النص، لا يكون اسم المتغير موجودًا. على النقيض من ذلك، في النطاق الديناميكي (أو النطاق الديناميكي )، إذا كان نطاق اسم متغير هو دالة معينة، فإن نطاقه هو الفترة الزمنية التي تُنفَّذ خلالها الدالة: أثناء تشغيل الدالة، يكون اسم المتغير موجودًا ومرتبطًا بقيمته، ولكن بعد انتهاء الدالة، لا يكون اسم المتغير موجودًا. هذا يعني أنه إذا fاستدعت الدالة دالةً مُعرَّفة بشكل منفصل g، ففي النطاق المعجمي، لا يمكن للدالة gالوصول إلى المتغيرات fالمحلية للدالة (بافتراض أن نص الدالة gليس داخل نص الدالة f)، بينما في النطاق الديناميكي، gيمكن للدالة الوصول إلى fالمتغيرات المحلية للدالة (لأن gالدالة تُستدعى أثناء استدعاء الدالة f).
$ # لغة باش $ x = 1 $ function g () { echo $x ; x = 2 ; } $ function f () { local x = 3 ; g ; } $ f # هل يطبع هذا 1 أم 3؟ 3 $ echo $x # هل يطبع هذا 1 أم 2؟ 1لنأخذ على سبيل المثال البرنامج الموجود على اليمين. يقوم السطر الأول بإنشاء متغير عام وتهيئته بالقيمة . يُعرّف السطر الثاني دالة تطبع ("تعكس") القيمة الحالية لـ ، ثم تُعيّنها إلى (مستبدلةً القيمة السابقة). يُعرّف السطر الثالث دالة تُنشئ متغيرًا محليًا (مخفيةً المتغير العام الذي يحمل نفس الاسم) وتُهيئه بالقيمة ، ثم تستدعي . يستدعي السطر الرابع الدالة . يطبع السطر الخامس القيمة الحالية لـ .x=1x1functiong(){echo$x;x=2;}gxx2functionf(){localx=3;g;}fx3gffecho$xx
تحدد قواعد النطاق ما يطبعه هذا البرنامج. إذا كانت لغة البرنامج تستخدم النطاق المعجمي، فإنه gيطبع ويعدل المتغير العام x(لأنه gمُعرَّف خارجه f)، لذا يطبع البرنامج 1ثم 2. على النقيض من ذلك، إذا كانت هذه اللغة تستخدم النطاق الديناميكي، فإنه gيطبع ويعدل fالمتغير المحلي x(لأنه gيُستدعى من داخله f)، لذا يطبع البرنامج 3ثم 1. (في الواقع، لغة البرنامج هي Bash ، التي تستخدم النطاق الديناميكي؛ لذا يطبع البرنامج 3ثم 1. إذا تم تشغيل نفس الكود باستخدام ksh93 التي تستخدم النطاق المعجمي، فستكون النتائج مختلفة.)
النطاق المعجمي
في النطاق المعجمي ، يشير الاسم دائمًا إلى سياقه المعجمي. هذه خاصية من خصائص نص البرنامج، وتجعلها لغة البرمجة مستقلة عن مكدس استدعاءات وقت التشغيل. ولأن هذه المطابقة لا تتطلب سوى تحليل نص البرنامج الثابت، يُطلق على هذا النوع من النطاق أيضًا اسم النطاق الثابت . يُعد النطاق المعجمي معيارًا في جميع اللغات المبنية على لغة ALGOL ، مثل باسكال ومودولا-2 وآدا ، وفي اللغات الوظيفية الحديثة مثل ML وهاسكل . كما يُستخدم في لغة C وما يرتبط بها من لغات نحوية ودلالية، وإن كان ذلك مع قيود مختلفة. يسمح النطاق الثابت للمبرمج بالتعامل مع مراجع الكائنات، مثل المعاملات والمتغيرات والثوابت والأنواع والدوال، وما إلى ذلك، كاستبدالات بسيطة للأسماء. هذا يُسهّل كثيرًا كتابة التعليمات البرمجية المعيارية والتعامل معها، حيث يمكن فهم بنية التسمية المحلية بمعزل عن غيرها. في المقابل، يُجبر النطاق الديناميكي المبرمج على توقع جميع سياقات التنفيذ المحتملة التي قد يُستدعى فيها رمز الوحدة.
البرنامج ف ; فار الأول : عدد صحيح ; ك : شار ؛الإجراء أ ؛ فار ك : حقيقي ؛ ل : عدد صحيح ;الإجراء ب ؛ المتغير م : حقيقي ؛ ابدأ (*نطاق ب+أ+ب*) نهاية ؛(*scope P+A*) end ;(*scope P*) end .على سبيل المثال، لغة باسكال ذات نطاق معجمي. لننظر إلى جزء برنامج باسكال على اليمين. المتغير Iمرئي في جميع المواضع، لأنه لا يُخفى أبدًا بواسطة متغير آخر يحمل نفس الاسم. charالمتغير Kمرئي فقط في البرنامج الرئيسي لأنه مخفي بواسطة realالمتغير Kالمرئي فقط في الإجراءات Aو B. المتغير Lمرئي أيضًا فقط في الإجراء Aو ، Bولكنه لا يُخفي أي متغير آخر. المتغير Mمرئي فقط في الإجراء B، وبالتالي لا يمكن الوصول إليه لا من الإجراء Aولا من البرنامج الرئيسي. كذلك، الإجراء Bمرئي فقط في الإجراء A، وبالتالي لا يمكن استدعاؤه من البرنامج الرئيسي. قد يكون هناك إجراء آخر باسم Bمُعلن عنه في البرنامج خارج الإجراء B. يحدد موضع Bذكر في البرنامج أيًا من الإجراءين المذكورين Bيُمثله، على غرار نطاق المتغيرات.
إنّ تطبيق النطاق المعجمي بشكل صحيح في اللغات التي تدعم الدوال المتداخلة من الدرجة الأولى ليس بالأمر الهيّن، إذ يتطلب أن تحمل كل قيمة دالة سجلاً بقيم المتغيرات التي تعتمد عليها (يُطلق على زوج الدالة وسياقها اسم " الإغلاق "). وبحسب طريقة التنفيذ وبنية الحاسوب ، قد يصبح البحث عن المتغيرات غير فعّال بعض الشيء عند استخدام دوال متداخلة معجميًا بعمق كبير ، على الرغم من وجود تقنيات معروفة للتخفيف من هذه المشكلة. [ 9 ] [ 10 ] كذلك، بالنسبة للدوال المتداخلة التي تشير فقط إلى وسائطها الخاصة والمتغيرات المحلية (مباشرةً)، يمكن معرفة جميع المواقع النسبية في وقت الترجمة . وبالتالي، لا توجد أيّة تكلفة إضافية عند استخدام هذا النوع من الدوال المتداخلة. وينطبق الأمر نفسه على أجزاء معينة من البرنامج لا تُستخدم فيها الدوال المتداخلة، وبالطبع، على البرامج المكتوبة بلغة لا تدعم الدوال المتداخلة (مثل لغة C).
تاريخ
استُخدم النطاق المعجمي لأول مرة في أوائل الستينيات للغة الإجرائية ALGOL 60، وتم اعتماده في معظم اللغات الإجرائية الأخرى منذ ذلك الحين. [ 4 ]
لطالما كان للغات مثل باسكال وسي نطاق معجمي، حيث أن كلاهما متأثر بالأفكار التي دخلت في ALGOL 60 و ALGOL 68 (على الرغم من أن لغة سي لم تتضمن وظائف متداخلة معجميًا ).
لغة بيرل هي لغة ذات نطاق ديناميكي، وقد أُضيف إليها نطاق ثابت لاحقاً.
استخدم مترجم لغة ليسب الأصلي (1960) النطاق الديناميكي. تم تقديم الربط العميق ، الذي يقارب النطاق الثابت (المعجمي)، حوالي عام 1962 في ليسب 1.5 (عبر جهاز Funarg الذي طوره ستيف راسل ، الذي كان يعمل تحت إشراف جون مكارثي ).
استخدمت جميع لغات ليسب المبكرة النطاق الديناميكي، عندما كانت تعتمد على المفسرات. في عام 1982، نشر جاي إل. ستيل الابن ومجموعة كومون ليسب كتابًا بعنوان "نظرة عامة على كومون ليسب " [ 11 ]، وهو عبارة عن مراجعة موجزة لتاريخ لغة ليسب وتطبيقاتها المختلفة حتى ذلك الحين، بالإضافة إلى استعراض للميزات التي ينبغي أن يتضمنها تطبيق كومون ليسب . في الصفحة 102، نقرأ:
معظم تطبيقات لغة ليسب تعاني من عدم اتساق داخلي، حيث قد يُسند المفسر والمترجم، افتراضيًا، دلالات مختلفة للبرامج الصحيحة؛ ويعود ذلك أساسًا إلى افتراض المفسر أن جميع المتغيرات ذات نطاق ديناميكي، بينما يفترض المترجم أنها محلية ما لم يُجبر على خلاف ذلك. وقد تم ذلك تسهيلًا للعملية وتحسينًا للكفاءة، ولكنه قد يؤدي إلى أخطاء دقيقة للغاية. يتجنب تعريف لغة ليسب المشتركة هذه التناقضات من خلال اشتراطه صراحةً على المفسر والمترجم فرض دلالات متطابقة على البرامج الصحيحة.
لذا، كان من الضروري أن تتمتع تطبيقات لغة Common Lisp بنطاق معجمي . مرة أخرى، من نظرة عامة على Common Lisp :
بالإضافة إلى ذلك، توفر لغة Common LISP الميزات التالية (معظمها مُستعار من MacLisp أو InterLisp أو Lisp Machines Lisp): (...) متغيرات ذات نطاق معجمي كامل. تم حل ما يُسمى بـ " مشكلة FUNARG " [ 12 ] [ 13 ] بشكل كامل، في كلتا الحالتين التنازلية والتصاعدية.
في نفس العام الذي نُشر فيه كتاب "نظرة عامة على لغة ليسب الشائعة" (1982)، نُشرت التصاميم الأولية (أيضًا من قِبل جاي إل. ستيل الابن) للغة ليسب مُجمّعة ذات نطاق معجمي، تُسمى "سكيم" ، وكانت تُجرى محاولات لتنفيذها بواسطة المُجمّعات. في ذلك الوقت، كان يُخشى عمومًا أن يكون النطاق المعجمي في لغة ليسب غير فعال من حيث التنفيذ. في كتاب "تاريخ لغة تي" ، [ 14 ] يكتب أولين شيفرز:
كانت جميع لغات Lisp الجادة المستخدمة في الإنتاج آنذاك تعتمد على النطاق الديناميكي. لم يكن أحد ممن لم يقرأوا بعناية أطروحة Rabbit [ 15 ] (التي كتبها جاي لويس ستيل الابن عام 1978) يعتقد أن النطاق المعجمي سينجح؛ حتى القلة الذين قرأوها كانوا يغامرون قليلاً بالاعتقاد بأن هذا سينجح في الاستخدام الإنتاجي الجاد.
يعود مصطلح "النطاق المعجمي" إلى عام 1967 على الأقل، [ 16 ] بينما يعود مصطلح "تحديد النطاق المعجمي" إلى عام 1970 على الأقل، حيث استُخدم في مشروع MAC لوصف قواعد نطاق لهجة Lisp MDL (المعروفة آنذاك باسم "Muddle"). [ 17 ]
النطاق المعجمي في البرمجة الحديثة
في لغات البرمجة الحديثة، يلعب النطاق المعجمي دورًا محوريًا في تمكين نماذج البرمجة الوظيفية. تعتمد لغات مثل جافا سكريبت [ 18 ] ، وبايثون [ 19 ] ، وسويفت [ 20 ] اعتمادًا كبيرًا على النطاق المعجمي لضمان قدرة الدوال على الوصول إلى المتغيرات من سياق تعريفها، حتى لو تم تنفيذ الدالة خارج نطاقها المعجمي. يكتسب هذا أهمية خاصة عند التعامل مع الدوال المغلقة، التي تُعد نتيجة مباشرة للنطاق المعجمي.
على سبيل المثال، في جافا سكريبت، تُستخدم الدوال المغلقة بكثرة في البرمجة غير المتزامنة ومعالجة الأحداث، إذ تسمح للدوال المستدعاة بالاحتفاظ بإمكانية الوصول إلى المتغيرات من النطاق الخارجي، مما يُسهّل كتابة كود أكثر وضوحًا ونمطية. وبالمثل، تستخدم بايثون وسويفت النطاق المعجمي لتنفيذ الدوال المغلقة وتمكين أنماط قوية مثل الدوال عالية الرتبة. [ 21 ]
نطاق ديناميكي
في النطاق الديناميكي ، يشير الاسم إلى سياق التنفيذ. من الناحية التقنية، يعني هذا أن لكل اسم مكدسًا عامًا للروابط. يؤدي تعريف متغير محلي باسم معين xإلى إضافة رابط إلى xالمكدس العام (الذي قد يكون فارغًا)، والذي يُزال عند خروج مسار التحكم من النطاق. xينتج عن التقييم في أي سياق دائمًا الرابط العلوي. تجدر الإشارة إلى أنه لا يمكن القيام بذلك أثناء الترجمة لأن مكدس الروابط موجود فقط أثناء التشغيل ، ولهذا يُسمى هذا النوع من النطاق بالنطاق الديناميكي .
يُعد النطاق الديناميكي غير شائع في اللغات الحديثة. [ 4 ]
بشكل عام، تُعرَّف بعض الكتل البرمجية لإنشاء روابط يكون عمرها مساويًا لوقت تنفيذ الكتلة؛ وهذا يُضيف بعض خصائص النطاق الثابت إلى عملية النطاق الديناميكي. مع ذلك، نظرًا لإمكانية استدعاء جزء من التعليمات البرمجية من مواقع وحالات مختلفة، قد يصعب تحديد الروابط التي ستُطبَّق عند استخدام متغير (أو حتى تحديد وجوده من الأساس). قد يكون هذا مفيدًا؛ إذ يُشير تطبيق مبدأ أقل قدر من المعرفة إلى ضرورة تجنُّب الاعتماد على أسباب (أو ظروف) قيمة المتغير، واستخدام القيمة وفقًا لتعريف المتغير. يُمكن لهذا التفسير المُقيَّد للبيانات المُشتركة أن يُوفِّر نظامًا مرنًا للغاية لتكييف سلوك الدالة مع الحالة (أو السياسة) الحالية للنظام. مع ذلك، تعتمد هذه الميزة على توثيق دقيق لجميع المتغيرات المُستخدمة بهذه الطريقة، وعلى تجنُّب الافتراضات حول سلوك المتغير، ولا تُوفِّر أي آلية للكشف عن التداخل بين أجزاء البرنامج المختلفة. تسمح بعض اللغات، مثل بيرل وكومون ليسب ، للمبرمج باختيار النطاق الثابت أو الديناميكي عند تعريف أو إعادة تعريف متغير. ومن أمثلة اللغات التي تستخدم النطاق الديناميكي لغة Logo ، ولغة Emacs Lisp ، ولغة LaTeX، ولغات الصدفة bash ، و dash ، و PowerShell .
يُعدّ النطاق الديناميكي سهل التنفيذ نسبيًا. للعثور على قيمة اسمٍ ما، يمكن للبرنامج اجتياز مكدس وقت التشغيل، والتحقق من كل سجل تنشيط (إطار مكدس كل دالة) بحثًا عن قيمة لهذا الاسم. عمليًا، يُصبح هذا أكثر كفاءةً باستخدام قائمة ارتباط ، وهي عبارة عن مكدس من أزواج الاسم/القيمة. تُضاف الأزواج إلى هذا المكدس عند كل تعريف، وتُحذف عند خروج المتغيرات عن سياقها. [ 22 ] يُعدّ الربط السطحي استراتيجية بديلة أسرع بكثير، إذ تستخدم جدول مرجعي مركزي يربط كل اسم بمكدس معانيه الخاص. هذا يُجنّب البحث الخطي أثناء وقت التشغيل للعثور على اسم معين، ولكن يجب الحرص على صيانة هذا الجدول بشكل صحيح. [ 22 ] تجدر الإشارة إلى أن كلتا الاستراتيجيتين تفترضان ترتيبًا للربط وفقًا لأسلوب "آخر ما يدخل أول ما يخرج" ( LIFO ) لأي متغير؛ عمليًا، جميع عمليات الربط مُرتبة بهذا الشكل.
يُعدّ تمثيل المتغيرات الديناميكية باستخدام متغيرات عامة بسيطة تطبيقًا أبسط. يتم الربط المحلي بحفظ القيمة الأصلية في موقع مجهول على المكدس، غير مرئي للبرنامج. عند انتهاء نطاق الربط، تُستعاد القيمة الأصلية من هذا الموقع. في الواقع، نشأ النطاق الديناميكي بهذه الطريقة. استخدمت التطبيقات الأولى للغة ليسب هذه الاستراتيجية الواضحة لتنفيذ المتغيرات المحلية، ولا تزال هذه الممارسة مستخدمة في بعض اللهجات، مثل GNU Emacs Lisp. أُدخل النطاق المعجمي إلى ليسب لاحقًا. وهو يُعادل مخطط الربط السطحي المذكور أعلاه، باستثناء أن جدول المرجع المركزي هو ببساطة سياق ربط المتغير العام، حيث يكون المعنى الحالي للمتغير هو قيمته العامة. لا يُعدّ الحفاظ على المتغيرات العامة أمرًا معقدًا. على سبيل المثال، يمكن أن يحتوي كائن رمزي على خانة مخصصة لقيمته العامة.
يوفر النطاق الديناميكي تجريدًا ممتازًا للتخزين المحلي للخيوط ، ولكن عند استخدامه بهذه الطريقة، لا يمكن الاعتماد على حفظ واستعادة متغير عام. تتمثل إحدى استراتيجيات التنفيذ الممكنة في أن يكون لكل متغير مفتاح محلي خاص بالخيط. عند الوصول إلى المتغير، يُستخدم المفتاح المحلي للوصول إلى موقع الذاكرة الخاص بالخيط (بواسطة كود مُولّد بواسطة المُصرّف، الذي يعرف المتغيرات الديناميكية والمتغيرات المعجمية). إذا لم يكن هناك مفتاح محلي خاص بالخيط المُستدعي، فسيتم استخدام الموقع العام. عند ربط متغير محليًا، تُخزّن قيمته السابقة في موقع مخفي على المكدس. يُنشأ التخزين المحلي الخاص بالخيط تحت مفتاح المتغير، وتُخزّن القيمة الجديدة هناك. تقوم عمليات التجاوز المتداخلة اللاحقة للمتغير داخل ذلك الخيط ببساطة بحفظ واستعادة هذا الموقع المحلي الخاص بالخيط. عند انتهاء سياق التجاوز الأولي الخارجي، يُحذف المفتاح المحلي الخاص بالخيط، مما يُتيح الوصول إلى النسخة العامة من المتغير مرة أخرى لذلك الخيط.
مع الشفافية المرجعية، يقتصر النطاق الديناميكي على مكدس الوسائط للدالة الحالية فقط، ويتطابق مع النطاق المعجمي.
التوسع الكلي
في اللغات الحديثة، يُعدّ توسيع الماكرو في المعالج المسبق مثالًا رئيسيًا على النطاق الديناميكي الفعلي. لغة الماكرو نفسها تُحوّل شفرة المصدر فقط، دون حلّ الأسماء، ولكن بما أن التوسيع يتم في مكانه، فعندما تُحلّ الأسماء في النص الموسّع (خاصةً المتغيرات الحرة)، تُحلّ بناءً على مكان توسيعها (أو "استدعائها")، كما لو كان النطاق الديناميكي مُطبّقًا.
يتمتع معالج C المسبق ، المستخدم لتوسيع وحدات الماكرو ، بنطاق ديناميكي فعلي، حيث إنه لا يحل الأسماء بنفسه وهو مستقل عن مكان تعريف الماكرو. على سبيل المثال، الماكرو:
#define ADD_A(x) x + aسيتم توسيع المتغير لإضافة قيمة aإلى المتغير المُمرر، ولن يتم تحديد هذا الاسم إلا لاحقًا بواسطة المُصرّف بناءً على مكان ADD_Aاستدعاء الماكرو (أو توسيعه). في الواقع، يقوم مُعالج C المُسبق فقط بالتحليل المعجمي ، حيث يقوم بتوسيع الماكرو أثناء مرحلة التجزئة، ولكنه لا يقوم بتحليله إلى شجرة بناء الجملة أو تحديد اسمه.
على سبيل المثال، في الكود التالي، يتم حل الاسم aالموجود في الماكرو (بعد التوسيع) إلى المتغير المحلي في موقع التوسيع:
#define ADD_A(x) (x + a)void add_one ( int * x ) { const int a = 1 ; * x = ADD_A ( * x ); }void add_two ( int * x ) { const int a = 2 ; * x = ADD_A ( * x ); }الأسماء المؤهلة
كما رأينا، فإن أحد الأسباب الرئيسية لنطاق المتغيرات هو أنه يساعد على منع تضارب الأسماء، إذ يسمح للأسماء المتطابقة بالإشارة إلى أشياء مختلفة، مع اشتراط أن يكون لكل اسم نطاقه الخاص. أحيانًا يكون هذا الشرط غير ملائم؛ فعندما يحتاج البرنامج إلى الوصول إلى العديد من العناصر المختلفة، فإنها عادةً ما تحتاج إلى أسماء ذات نطاق عام، مما يستدعي استخدام تقنيات مختلفة لتجنب تضارب الأسماء.
لمعالجة هذه المشكلة، توفر العديد من لغات البرمجة آليات لتنظيم الأسماء العامة. وتختلف تفاصيل هذه الآليات والمصطلحات المستخدمة باختلاف اللغة، ولكن الفكرة العامة هي أنه يمكن إعطاء مجموعة من الأسماء اسمًا خاصًا بها - بادئة - وعند الضرورة، يمكن الإشارة إلى كيان ما باسم مؤهل يتكون من الاسم بالإضافة إلى البادئة. عادةً ما يكون لهذه الأسماء نطاقان: نطاق (عادةً النطاق العام) يظهر فيه الاسم المؤهل، ونطاق واحد أو أكثر أضيق يظهر فيه الاسم غير المؤهل ( بدون البادئة) أيضًا. وعادةً ما يمكن تنظيم هذه المجموعات نفسها في مجموعات فرعية؛ أي يمكن أن تكون متداخلة .
على الرغم من أن العديد من اللغات تدعم هذا المفهوم، إلا أن التفاصيل تختلف اختلافًا كبيرًا. فبعض اللغات لديها آليات، مثل مساحات الأسماء في لغتي C++ و C# ، تُستخدم بشكل شبه حصري لتمكين تنظيم الأسماء العامة في مجموعات. بينما تمتلك لغات أخرى آليات، مثل الحزم في لغة Ada والهياكل في لغة Standard ML ، تجمع بين هذا الغرض ووظيفة إضافية تتمثل في السماح لبعض الأسماء بأن تكون مرئية فقط لأعضاء مجموعتها. وغالبًا ما تسمح اللغات الكائنية التوجه للفئات أو الكائنات الفردية بتحقيق هذا الغرض (سواءً كانت تمتلك آلية أخرى لهذا الغرض الأساسي أم لا ) . علاوة على ذلك، غالبًا ما تدمج اللغات هذه الأساليب؛ فعلى سبيل المثال، تُشبه حزم لغة Perl إلى حد كبير مساحات الأسماء في لغة C++، ولكنها تُستخدم اختياريًا كفئات للبرمجة الكائنية التوجه؛ بينما تُنظم لغة Java متغيراتها ووظائفها في فئات، ثم تُنظم هذه الفئات في حزم شبيهة بحزم لغة Ada.
حسب اللغة
فيما يلي قواعد نطاق اللغات التمثيلية.
ج
في لغة C، يُعرف النطاق تقليديًا بالربط أو الرؤية ، خاصةً بالنسبة للمتغيرات. C لغة ذات نطاق معجمي، حيث يوجد نطاق عام (يُعرف بالربط الخارجي )، ونوع من نطاق الوحدة أو نطاق الملف (يُعرف بالربط الداخلي )، ونطاق محلي (داخل الدالة)؛ ويمكن تداخل النطاقات داخل الدالة عبر نطاق الكتلة. مع ذلك، لا تدعم لغة C القياسية الدوال المتداخلة.
يُحدد نوع تخزين المتغير ونطاق رؤيته . توجد ثلاثة أنواع من فترات العمر في لغة C: ثابت (تنفيذ البرنامج)، وتلقائي (تنفيذ الكتلة، مُخصص على المكدس)، ويدوي (مُخصص على الكومة). يدعم المُصرّف النوعين الثابت والتلقائي فقط من فترات العمر، بينما يجب تتبع الذاكرة المُخصصة يدويًا بين المتغيرات المختلفة. توجد ثلاثة مستويات للرؤية في لغة C: الربط الخارجي (عام)، والربط الداخلي (على مستوى الملف تقريبًا)، ونطاق الكتلة (الذي يشمل الدوال)؛ يمكن تداخل نطاقات الكتل، كما يُمكن تحقيق مستويات مختلفة من الربط الداخلي باستخدام تضمينات C. يُشير الربط الداخلي في لغة C إلى الرؤية على مستوى وحدة الترجمة ، أي ملف المصدر بعد معالجته بواسطة مُعالج C المُسبق ، ويشمل ذلك جميع التضمينات ذات الصلة.
تُجمَّع برامج لغة C كملفات كائنية منفصلة ، ثم تُربط هذه الملفات في ملف تنفيذي أو مكتبة عبر رابط . وبالتالي، يتم تقسيم عملية حلّ الأسماء بين المُجمِّع، الذي يحلّ الأسماء داخل وحدة الترجمة (أو "وحدة التجميع" بشكل أعم، ولكن هذا مفهوم مختلف تمامًا)، والرابط، الذي يحلّ الأسماء بين وحدات الترجمة؛ انظر الربط لمزيد من التفاصيل.
في لغة C، تدخل المتغيرات ذات النطاق الكتلي إلى سياقها عند تعريفها (وليس في بداية الكتلة)، وتخرج من هذا السياق عند استدعاء أي دالة (غير متداخلة) داخل الكتلة، وتعود إلى السياق عند انتهاء الدالة، وتخرج منه عند نهاية الكتلة. أما بالنسبة للمتغيرات المحلية التلقائية، فيتم تخصيصها عند تعريفها وتحريرها عند نهاية الكتلة، بينما بالنسبة للمتغيرات المحلية الثابتة، فيتم تخصيصها عند بدء تشغيل البرنامج وتحريرها عند انتهائه.
يوضح البرنامج التالي متغيرًا بنطاق كتلة يدخل في السياق في منتصف الكتلة، ثم يخرج من السياق (ويتم تحريره فعليًا) عند انتهاء الكتلة:
#include <stdio.h>int main ( void ) { char x = 'm' ; printf ( "%c \n " , x );{ printf ( "%c \n " , x ); char x = 'b' ; printf ( "%c \n " , x ); }printf ( "%c \n " , x ); }يُخرج البرنامج ما يلي:
م م ب م
توجد مستويات أخرى لنطاق المتغيرات في لغة C. [ 23 ] أسماء المتغيرات المستخدمة في نموذج الدالة لها نطاق رؤية نموذج الدالة، ويتم الخروج من السياق في نهاية نموذج الدالة. ولأن الاسم غير مستخدم، فإن هذا غير مفيد للترجمة، ولكنه قد يكون مفيدًا للتوثيق. أسماء التسميات لعبارة GOTO لها نطاق الدالة.
لغة سي++
يجب تعريف جميع المتغيرات المستخدمة في البرنامج مع تحديد نوعها في موضع سابق من الكود، كما هو الحال في الكود السابق عند بداية جسم الدالة mainعند تعريف a و b و result على أنها من نوع int. يمكن أن يكون نطاق المتغير عامًا أو محليًا. المتغير العام هو المتغير المُعرَّف في الجزء الرئيسي من الكود المصدري، خارج جميع الدوال، بينما المتغير المحلي هو المتغير المُعرَّف داخل جسم دالة أو كتلة برمجية.
تسمح الإصدارات الحديثة بنطاق معجمي متداخل.
سويفت
يحتوي Swift على قاعدة مماثلة للنطاقات مع C++، ولكنه يحتوي على مُعدِّلات وصول مختلفة .
| المعدل | النطاق الفوري | ملف | يحتوي على وحدة/حزمة | بقية العالم |
|---|---|---|---|---|
| يفتح | نعم | نعم | نعم | نعم، يسمح بالفئات الفرعية |
| عام | نعم | نعم | نعم | نعم، يمنع الأصناف الفرعية |
| داخلي | نعم | نعم | نعم | لا |
| ملف خاص | نعم | نعم | لا | لا |
| خاص | نعم | لا | لا | لا |
يذهب
تعتمد لغة Go على النطاق المعجمي باستخدام الكتل. [ 3 ]
جافا
لغة جافا ذات نطاق معجمي.
تحتوي فئة جافا على عدة أنواع من المتغيرات: [ 24 ]
- المتغيرات المحلية
- تُعرَّف هذه المتغيرات داخل دالة أو كتلة برمجية محددة. وتكون هذه المتغيرات محلية في مكان تعريفها والمستويات الأدنى منه. على سبيل المثال، يمكن لحلقة تكرار داخل دالة استخدام المتغيرات المحلية لتلك الدالة، ولكن ليس العكس. تُحذف متغيرات الحلقة (المحلية لتلك الحلقة) بمجرد انتهاء الحلقة.
- المتغيرات الأعضاء
- تُعرف المتغيرات ، التي تُسمى أيضًا بالحقول، بأنها متغيرات مُعلنة داخل الصنف، خارج أي دالة. وبشكل افتراضي، تكون هذه المتغيرات متاحة لجميع الدوال داخل ذلك الصنف، وكذلك لجميع الأصناف في الحزمة.
- حدود
- هي متغيرات في تعريفات الأساليب.
بشكل عام، تحدد مجموعة من الأقواس نطاقًا معينًا، لكن سلوك المتغيرات في المستوى الأعلى داخل الصنف قد يختلف تبعًا لكلمات التعديل المستخدمة في تعريفها. يوضح الجدول التالي إمكانية الوصول إلى الأعضاء المسموح بها لكل مُعدِّل. [ 25 ]
| المعدل | فصل | طَرد | فئة فرعية | عالم |
|---|---|---|---|---|
| عام | نعم | نعم | نعم | نعم |
| محمي | نعم | نعم | نعم | لا |
| (بدون مُعدِّل) | نعم | نعم | لا | لا |
| خاص | نعم | لا | لا | لا |
جافا سكريبت
تتميز لغة جافا سكريبت بقواعد نطاق بسيطة ، [ 26 ] لكن قواعد تهيئة المتغيرات وحلّ الأسماء قد تُسبب مشاكل، كما أن الاستخدام الواسع للدوال المغلقة في عمليات الاستدعاء يعني أن السياق المعجمي للدالة عند تعريفها (المستخدم لحلّ الأسماء) قد يختلف اختلافًا كبيرًا عن السياق المعجمي عند استدعائها (وهو غير ذي صلة بحلّ الأسماء). تتمتع كائنات جافا سكريبت بحلّ أسماء للخصائص، لكن هذا موضوع منفصل.
تتميز لغة جافا سكريبت بنطاق معجمي [ 27 ] متداخل على مستوى الدالة، حيث يمثل السياق العام السياق الخارجي. يُستخدم هذا النطاق لكل من المتغيرات والدوال (أي تعريفات الدوال، وليس متغيرات من نوع دالة ). [ 28 ] يُعد نطاق الكتلة باستخدام الكلمتين المفتاحيتين `<block scope>` letو` const<block scope>` معيارًا منذ إصدار ECMAScript 6. يمكن إنشاء نطاق الكتلة بتغليف الكتلة بأكملها في دالة ثم تنفيذها؛ يُعرف هذا بنمط تعبير الدالة المُستدعاة فورًا (IIFE).
على الرغم من بساطة نطاق المتغيرات في جافا سكريبت - فهو نطاق معجمي ونطاق دالة - إلا أن قواعد التهيئة وحلّ الأسماء المرتبطة به تُسبب بعض الارتباك. أولًا، عند إسناد قيمة إلى اسم خارج النطاق، يتم إنشاء متغير عام جديد، وليس متغيرًا محليًا. ثانيًا، لإنشاء متغير محلي جديد، يجب استخدام الكلمة varالمفتاحية `input`؛ حيث يُنشأ المتغير في بداية الدالة بقيمة، ثم undefinedتُسند إليه قيمته عند الوصول إلى عبارة الإسناد.
- يتم تعيين قيمة تعبير التعيين الخاص بالمتغير الذي يحتوي على مُهيئ عند تنفيذ عبارة المتغير ، وليس عند إنشاء المتغير. [ 29 ]
يُعرف هذا باسم رفع المتغيرات [ 30 ] ، حيث يتم رفع تعريف المتغير، وليس تهيئته، إلى أعلى الدالة. ثالثًا، يؤدي الوصول إلى المتغيرات قبل تهيئتها إلى ظهور خطأ undefined، بدلًا من خطأ في بناء الجملة. رابعًا، بالنسبة لتعريفات الدوال، يتم رفع كل من التعريف والتهيئة إلى أعلى الدالة، على عكس تهيئة المتغيرات. على سبيل المثال، يُنتج الكود التالي مربع حوار مع مُخرجات.غير محددبما أن تعريف المتغير المحلي يتم رفعه، مما يؤدي إلى إخفاء المتغير العام، ولكن عملية التهيئة لا تتم، لذلك يكون المتغير غير مُعرَّف عند استخدامه:
a = 1 ; function f () { alert ( a ); var a = 2 ; } f ();علاوة على ذلك، بما أن الدوال تُعتبر كائنات من الدرجة الأولى في جافا سكريبت، وكثيرًا ما تُستخدم كدوال رد نداء أو تُعاد من دوال أخرى، فعند تنفيذ دالة ما، يعتمد تحديد اسمها على مكان تعريفها الأصلي (السياق المعجمي للتعريف)، وليس على السياق المعجمي أو سياق التنفيذ الذي تُستدعى فيه. وتُسمى النطاقات المتداخلة لدالة معينة (من الأكثر عمومية إلى الأكثر محلية) في جافا سكريبت، وخاصةً للدالة المغلقة المستخدمة كدالة رد نداء، أحيانًا بسلسلة النطاقات ، قياسًا على سلسلة النموذج الأولي للكائن.
يمكن إنشاء الدوال المغلقة في جافا سكريبت باستخدام الدوال المتداخلة، لأن الدوال كائنات من الدرجة الأولى. [ 31 ] يؤدي إرجاع دالة متداخلة من دالة محيطة إلى تضمين المتغيرات المحلية للدالة المحيطة كسياق معجمي (غير محلي) للدالة المُعادة، مما ينتج عنه دالة مغلقة. على سبيل المثال:
دالة newCounter () { // تُعيد عدادًا يتم زيادته عند الاستدعاء (يبدأ من 0) // ويُعيد قيمته الجديدة var a = 0 ; var b = function () { a ++ ; return a ; }; return b ; } c = newCounter (); alert ( c () + ' ' + c ()); // يُخرج "1 2"تُستخدم الدوال المغلقة بكثرة في جافا سكريبت، نظرًا لاستخدامها في دوال الاستدعاء. في الواقع، أي ربط لدالة في السياق المحلي كدالة استدعاء أو إعادتها من دالة يُنشئ دالة مغلقة إذا وُجدت أي متغيرات غير مُعرَّفة في جسم الدالة (مع سياق الدالة المغلقة المُستند إلى النطاقات المُتداخلة للسياق المعجمي الحالي، أو "سلسلة النطاق")؛ وقد يكون هذا غير مقصود. عند إنشاء دالة استدعاء بناءً على مُعاملات، يجب تخزين هذه المُعاملات في دالة مغلقة، وإلا فسيتم إنشاء دالة مغلقة تُشير عن طريق الخطأ إلى المُتغيرات في السياق المُحيط، والذي قد يتغير. [ 32 ]
يعتمد حل أسماء خصائص كائنات جافا سكريبت على الوراثة في شجرة النموذج الأولي - ويسمى المسار إلى الجذر في الشجرة سلسلة النموذج الأولي - وهو منفصل عن حل أسماء المتغيرات والوظائف.
التلعثم
تختلف قواعد نطاق اللغة في لهجات لغة ليسب .
استخدمت لغة Lisp الأصلية النطاق الديناميكي؛ أما لغة Scheme ، المستوحاة من لغة ALGOL ، فقد أدخلت النطاق الثابت (المعجمي) إلى عائلة Lisp.
استخدمت لغة ماكليسب النطاق الديناميكي افتراضيًا في المفسر والنطاق المعجمي افتراضيًا في الكود المُترجم، مع إمكانية وصول الكود المُترجم إلى الروابط الديناميكية باستخدام SPECIALتعريفات لمتغيرات محددة. [ 33 ] ومع ذلك، تعاملت ماكليسب مع الربط المعجمي كتحسين أكثر مما هو متوقع في اللغات الحديثة، ولم تتضمن ميزة الإغلاق التي قد يتوقعها المرء من النطاق المعجمي في لغات ليسب الحديثة. كانت هناك عملية منفصلة *FUNCTIONمتاحة للتغلب على بعض هذه المشكلة بطريقة غير سلسة. [ 34 ]
اعتمدت لغة Common Lisp النطاق المعجمي من لغة Scheme ، [ 35 ] كما فعلت لغة Clojure .
تتمتع لغة ISLISP بنطاق معجمي للمتغيرات العادية. كما أنها تحتوي على متغيرات ديناميكية، ولكنها في جميع الحالات يتم تمييزها بشكل صريح؛ يجب تعريفها بصيغة defdynamicخاصة، وربطها بصيغة dynamic-letخاصة، والوصول إليها بصيغة dynamicخاصة صريحة. [ 36 ]
لا تزال بعض لهجات لغة ليسب الأخرى، مثل إيماكس ليسب ، تستخدم النطاق الديناميكي افتراضيًا. أما إيماكس ليسب، فيتوفر لديها الآن نطاق معجمي متاح على أساس كل مخزن مؤقت. [ 37 ]
بايثون
بالنسبة للمتغيرات، يوفر بايثون نطاقات الدالة، ونطاق الوحدة، والنطاق العام. تدخل الأسماء إلى السياق عند بداية النطاق (نطاق الدالة، أو نطاق الوحدة، أو النطاق العام)، وتخرج منه عند استدعاء دالة غير متداخلة أو عند انتهاء النطاق. إذا تم استخدام اسم قبل تهيئة المتغير، فسيؤدي ذلك إلى ظهور استثناء وقت التشغيل. عند الوصول إلى متغير (دون إسناد قيمة إليه)، يتم حل الاسم وفقًا لقاعدة LEGB (محلي، محيط، عام، مدمج) التي تُحدد أضيق نطاق ذي صلة. مع ذلك، عند إسناد قيمة إلى متغير، يتم افتراضيًا تعريف متغير يبدأ نطاقه من بداية المستوى (الدالة، أو الوحدة، أو النطاق العام)، وليس من نقطة الإسناد. يمكن تجاوز هاتين القاعدتين باستخدام تعريف `<input> global` أو nonlocal`<input>` (في بايثون 3) قبل الاستخدام، مما يسمح بالوصول إلى المتغيرات العامة حتى في حال وجود متغير غير محلي مُقنّع، وإسناد القيم إلى المتغيرات العامة أو غير المحلية.
كمثال بسيط، تقوم الدالة بربط متغير بالنطاق العام:
def f () -> None : print ( x )x : str = "global" f () # يطبع: globalلاحظ أن xالدالة تُعرَّف قبل fاستدعاء الدالة، لذا لا يظهر أي خطأ، على الرغم من تعريفها بعد الإشارة إليها في تعريف الدالة f. من الناحية اللغوية، هذه إشارة أمامية ، وهو أمر مسموح به في بايثون.
هنا، يؤدي التعيين إلى إنشاء متغير محلي جديد، وهو ما لا يغير قيمة المتغير العام:
def f () -> None : x : str = "f" print ( x )x : str = "global" print ( x ) # يطبع: global f () # يطبع: f print ( x ) # يطبع: globalيؤدي إسناد قيمة إلى متغير داخل دالة إلى تعريفه كمتغير محلي ضمن نطاق تلك الدالة، وبالتالي فإن نطاقه يشمل الدالة بأكملها، واستخدامه قبل هذا الإسناد يُسبب خطأً. يختلف هذا عن لغة C، حيث يبدأ نطاق المتغير المحلي من لحظة تعريفه. هذا الكود يُسبب خطأً:
def f () -> None : print ( x ) x : str = "f"x : str = "global" f () # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # File "<stdin>", line 2, in f # UnboundLocalError: local variable 'x' referenced before assignmentيمكن تجاوز قواعد تحليل الأسماء الافتراضية باستخدام الكلمات المفتاحية global`or` nonlocal(في بايثون 3). في الكود أدناه، يعني global xالتصريح `in` أن `in` يُحل إلى المتغير العام. وبالتالي، يمكن الوصول إليه (لأنه مُعرّف مسبقًا)، وتُسند عملية الإسناد `assign` القيمة إلى المتغير العام، بدلًا من تعريف متغير محلي جديد. لاحظ أنه لا حاجة إلى تصريح `in` ، لأنه لا يُسند القيمة إلى المتغير، وبالتالي يُحل افتراضيًا إلى المتغير العام.gxglobalf
def f () -> None : print ( x )def g () -> None : global x print ( x ) x = "g"x : str = "global" f () # يطبع: global g () # يطبع: global f () # يطبع: gglobalيمكن استخدامها أيضًا مع الدوال المتداخلة. بالإضافة إلى السماح بتعيين قيمة لمتغير عام، كما هو الحال في الدوال غير المتداخلة، يمكن استخدامها أيضًا للوصول إلى المتغير العام في وجود متغير غير محلي.
def f () -> None : def g () -> None : global x print ( x ) x : str = "f" g ()x : str = "global" f () # يطبع: globalبالنسبة للدوال المتداخلة، يوجد أيضًا nonlocalتعريف لتعيين قيمة لمتغير غير محلي، على غرار استخدام globalفي دالة غير متداخلة:
def f () -> None : def g () -> None : nonlocal x # بايثون 3 فقط x = "g" x : str = "f" g () print ( x )x : str = "global" f () # يطبع: g print ( x ) # يطبع: globalR
لغة R هي لغة ذات نطاق معجمي، على عكس تطبيقات S الأخرى حيث تُحدد قيم المتغيرات الحرة بواسطة مجموعة من المتغيرات العامة، بينما في R تُحدد بواسطة السياق الذي أُنشئت فيه الدالة. [ 38 ] يمكن الوصول إلى سياقات النطاق باستخدام مجموعة متنوعة من الميزات (مثل parent.frame()) التي يمكنها محاكاة تجربة النطاق الديناميكي إذا رغب المبرمج بذلك.
لا يوجد نطاق كتلة:
a <- 1 { a <- 2 } message ( a ) ## 2تتمتع الدوال بإمكانية الوصول إلى النطاق الذي تم إنشاؤها فيه:
a <- 1 f <- function () { message ( a ) } f () ## 1تبقى المتغيرات التي يتم إنشاؤها أو تعديلها داخل دالة ما في مكانها:
a <- 1 f <- function () { message ( a ) a <- 2 message ( a ) } f () ## 1 ## 2 message ( a ) ## 1تبقى المتغيرات التي يتم إنشاؤها أو تعديلها داخل دالة ما في مكانها ما لم يُطلب صراحةً تعيينها إلى النطاق المحيط بها:
a <- 1 f <- function () { message ( a ) a <<- 2 message ( a ) } f () ## 1 ## 2 message ( a ) ## 2على الرغم من أن لغة R تتمتع بنطاق معجمي افتراضيًا، إلا أنه يمكن تغيير نطاقات الدوال:
a <- 1 f <- function () { message ( a ) } my_env <- new.env () my_env $ a <- 2 f () ## 1 environment ( f ) <- my_env f () ## 2ملحوظات
- ↑ انظر التعريف لمعرفة معنى "النطاق" مقابل "السياق".
- ↑ يعتمد "النطاق الديناميكي" على مدى (مدة الحياة)، وليس النطاق ، وبالتالي فهو غير دقيق رسميًا.
- ↑ على سبيل المثال، يستخدم محرك قوالب Jinja للغة Python بشكل افتراضي كلاً من النطاق المعجمي (للاستيرادات) والنطاق الديناميكي (للتضمينات)، ويسمح بتحديد السلوك باستخدام الكلمات الرئيسية؛ انظر سلوك سياق الاستيراد .
- ↑ يُعتبر مصطلحا "تحديد الاسم" و"ربط الاسم" مترادفين إلى حد كبير؛ فمن الناحية الدقيقة، يُحدد "تحديد الاسم" الاسم الذي يُشير إليه استخدامٌ مُعينٌ لاسمٍ ما، دون ربطه بأي معنى، كما هو الحال في بناء الجملة المجرد عالي المستوى ، بينما يربط "ربط الاسم" الاسم بمعنى فعلي. عمليًا، يُستخدم المصطلحان بشكلٍ مُتبادل.
- ↑ بالنسبة للتعليمات البرمجية ذاتية التعديل، يمكن أن يتغير السياق المعجمي نفسه أثناء وقت التشغيل.
- ↑ على النقيض من ذلك، فإن *"سياق ربط الاسم"، و*"ربط الاسم الذي يدخل في النطاق" أو *"ربط الاسم الذي يخرج من النطاق" كلها غير صحيحة - فربط الاسم له نطاق، بينما جزء من البرنامج له سياق.
مراجع
- ↑ "تقرير عن لغة الخوارزميات Algol 60"، 2.7. الكميات والأنواع والنطاقات
- ↑ WG14 N1256 (النسخة المحدثة لعام 2007 من معيار C99 )، 6.2.1 نطاقات المعرفات، 2007-09-07
- 1 2 مواصفات لغة البرمجة Go : التصريحات والنطاق ، إصدار 13 نوفمبر 2013
- 1 2 3 بورنينج أ. CSE 341 -- النطاق المعجمي والديناميكي . جامعة واشنطن.
- ↑ كروكفورد، دوغلاس. "اتفاقيات البرمجة للغة جافا سكريبت" . تم الاسترجاع في 2015-01-04 .
- ^ باكوس، جيه دبليو؛ ويجستين، JH. فان فيجنجاردن، أ؛ وودجر، م. باور، فلوريدا؛ جرين، ج. كاتز، C .؛ مكارثي، J .؛ بيرليس، AJ؛ روتيسهاوزر، H .؛ ساملسون، ك. فوكوا، ب. (1960). "تقرير عن اللغة الخوارزمية ALGOL 60" . اتصالات ACM . 3 (5): 299. دوى : 10.1145/367236.367262 . S2CID 278290 .
- ↑ "الدوال - جافا سكريبت: MDN" . 23 أبريل 2023.
لا يمكن الوصول إلى المتغيرات المُعرَّفة داخل دالة من أي مكان خارجها، لأن المتغير مُعرَّف فقط ضمن نطاق الدالة. مع ذلك، يمكن للدالة الوصول إلى جميع المتغيرات والدوال المُعرَّفة ضمن نطاقها.
- ↑ "N4720: مسودة عمل، إضافات إلى لغة C++ للوحدات النمطية" (ملف PDF) . مؤرشف (ملف PDF) من الأصل بتاريخ 30 أبريل 2019. تم الاطلاع عليه بتاريخ 30 أبريل 2019 .
- ↑ " براغماتية لغات البرمجة "، جدول رموز ليبلانك-كوك
- ↑ " تجريد جدول الرموز لتنفيذ اللغات ذات التحكم الصريح في النطاق "، ليبلانك-كوك، 1983
- ↑ لويس ستيل، جاي (أغسطس 1982). "نظرة عامة على لغة Common Lisp". وقائع ندوة ACM لعام 1982 حول لغة LISP والبرمجة الوظيفية - LFP '82 . الصفحات 98-107 . doi : 10.1145/800068.802140 . ISBN 0-89791-082-6. S2CID 14517358 .
- ↑ جويل، موسى (يونيو 1970). "وظيفة الدالة في لغة ليسب". مذكرة معهد ماساتشوستس للتكنولوجيا للذكاء الاصطناعي 199. مختبر الذكاء الاصطناعي بمعهد ماساتشوستس للتكنولوجيا.
- ↑ ستيل، جاي لويس الابن؛ سوسمان، جيرالد جاي (مايو 1978). "فن المترجم؛ أو، مُركّب النمطية (الأجزاء صفر، واحد، واثنان)". مذكرة معهد ماساتشوستس للتكنولوجيا للذكاء الاصطناعي 453. مختبر الذكاء الاصطناعي بمعهد ماساتشوستس للتكنولوجيا.
- ↑ شيفرز، أولين. "تاريخ تي" . بول غراهام . تم الاطلاع عليه في 5 فبراير 2020 .
- ↑ ستيل، جاي لويس الابن (مايو 1978). رابيت: مُجمِّع للغة سكيم (تقرير فني). معهد ماساتشوستس للتكنولوجيا. hdl : 1721.1/6913 .
- ↑ " النطاق المعجمي "، تنظيم الحاسوب والبرنامج، الجزء 3 ، ص 18، على كتب جوجل ، جامعة ميشيغان. مؤتمرات الهندسة الصيفية، 1967
- ↑ " النطاق المعجمي "، تقرير التقدم لمشروع MAC، المجلد 8 ، ص 80، على كتب جوجل ، 1970.
- ↑ "الإغلاقات" . 4 نوفمبر 2025.
- ↑ بوزو راموس، ليودانيس (16 يوليو 2025). "نطاق بايثون وقاعدة LEGB: حل الأسماء في التعليمات البرمجية الخاصة بك" .
- ↑ هدسون، بول (9 سبتمبر 2020). "اختر كلمة، أي كلمة: UIAlertController" .
- ↑ "وظائف بايثون" . 4 أكتوبر 2025.
- 1 2 سكوت 2009 ، 3.4 نطاق التنفيذ ، ص. 143.
- ↑ " نطاق "، XL C/C++ الإصدار 8.0 لنظام لينكس، IBM
- ↑ "تعريف متغيرات الأعضاء (دروس جافا > تعلم لغة جافا > الفئات والكائنات)" . docs.oracle.com . تم الاطلاع عليه بتاريخ 19 مارس 2018 .
- ↑ "التحكم في الوصول إلى أعضاء فئة (دروس جافا > تعلم لغة جافا > الفئات والكائنات)" . docs.oracle.com . تم الاطلاع عليه بتاريخ 19 مارس 2018 .
- ↑ " كل ما تحتاج لمعرفته حول نطاق متغيرات جافا سكريبت "، سوراب باراخ ، البرمجة رائعة ، 8 فبراير 2010
- ↑ "شرح ES5" . es5.github.io . تم الاطلاع عليه بتاريخ 19 مارس 2018 .
- ↑ "الدوال" . وثائق MDN على الويب . تم الاطلاع عليه بتاريخ 19 مارس 2018 .
- ↑ " 12.2 عبارة المتغير "، ECMAScript 5.1 المشروح، آخر تحديث: 2012-05-28
- ↑ " نطاق جافا سكريبت ورفع المتغيرات "، بن تشيري ، جيد بما فيه الكفاية ، 2010-02-08
- ↑ إغلاقات جافا سكريبت ، ريتشارد كورنفورد. مارس 2004
- ↑ " شرح نطاق جافا سكريبت والإغلاقات "، روبرت نيمان، 9 أكتوبر 2008
- ↑ بيتمان، كينت (16 ديسمبر 2007). "دليل ماكليسب المنقح (دليل بيتمان)، طبعة صباح الأحد" . MACLISP.info . شركة هايبرميتا. التصريحات والمترجم، مفهوم "المتغيرات" . تم الاسترجاع في 20 أكتوبر 2018.
إذا تم تعريف المتغير المراد ربطه على أنه خاص، يتم تجميع الربط كشفرة لمحاكاة طريقة ربط المفسر للمتغيرات
. - ↑ بيتمان، كينت (16 ديسمبر 2007). "دليل ماكليسب المنقح (دليل بيتمان)، طبعة صباح الأحد" . MACLISP.info . شركة هايبرميتا. المُقيِّم، نموذج خاص . تم الاسترجاع في 20 أكتوبر 2018.
يهدف هذا الدليل إلى المساعدة في حل "
مشكلة funarg
"، ولكنه لا يعمل إلا في بعض الحالات البسيطة
.*FUNCTION*FUNCTION - ↑ بيتمان، كينت؛ وآخرون . (نسخة الويب من معيار ANSI X3.226-1994) (1996). "Common Lisp HyperSpec" . Lispworks.com . LispWorks Ltd. 1.1.2 التاريخ . تم الاسترجاع في 20 أكتوبر 2018.
حسّنت لغة MacLisp مفهوم المتغيرات الخاصة في لغة Lisp 1.5... كانت التأثيرات الرئيسية على لغة Common Lisp هي Lisp Machine Lisp وMacLisp وNIL وS-1 Lisp وSpice Lisp وScheme.
- ↑ "لغة البرمجة ISLISP، مسودة عمل ISLISP 23.0" (ملف PDF) . ISLISP.info . 11.1 المبدأ المعجمي . تم الاطلاع عليه في 20 أكتوبر 2018.
يتم إنشاء الروابط الديناميكية والوصول إليها بواسطة آلية منفصلة (أي،
و
، و
)
.defdynamicdynamic-letdynamic - ↑ " الربط المعجمي" . EmacsWiki . تم الاسترجاع في 20 أكتوبر 2018.
يحتوي Emacs 24 على ربط معجمي اختياري، والذي يمكن تفعيله على أساس كل مخزن مؤقت.
- ↑ "الأسئلة الشائعة حول لغة R" . cran.r-project.org . تم الاطلاع عليه بتاريخ 19 مارس 2018 .
- أبيلسون، هارولد ؛ سوسمان، جيرالد جاي ؛ سوسمان، جولي (1996) [1984]. بنية وتفسير برامج الحاسوب . كامبريدج، ماساتشوستس: مطبعة معهد ماساتشوستس للتكنولوجيا . ISBN 0-262-51087-1.
- سكوت، مايكل ل. (2009) [2000]. براغماتية لغات البرمجة ( الطبعة الثالثة). دار مورغان كوفمان للنشر. ISBN 978-0-12-374514-9.
- الفصل الثالث: الأسماء والنطاقات والتجليد، الصفحات 111-174
- القسم 13.4.1: لغات البرمجة النصية: الميزات المبتكرة: الأسماء والنطاقات، الصفحات 691-699
- مفاهيم لغة البرمجة
- مقارنات لغات البرمجة
