תחום הכרזה

בתכנות מחשבים, תְּחוּם הַכְרָזָה של שם משתנה הוא החלק של התוכנית שבו הקישור בין השם למשתנה תקף; כלומר, היכן שניתן להשתמש בשם כדי להתייחס לישות. בחלקים אחרים של התוכנית, השם עשוי להתייחס לישות אחרת (ייתכן שיש לו הכרזה אחרת), או לא להתייחס לשום דבר (יכול להיות שהוא לא מוגדר). תחום הכרזה מסייע במניעת התנגשות שמות בכך שהוא מאפשר לאותו שם להתייחס לאובייקטים שונים - כל עוד לשמות יש תחומים נפרדים. תחום ההכרזה של שם משתנה ידוע גם כנראות של ישות, במיוחד בספרות ישנה או טכנית יותר - הנראות היא מנקודת המבט של הישות המוזכרת, לעומת תחום ההכרזה שמתמקד בשם המפנה.

המונח "תחום הכרזה" משמש גם להתייחסות לקבוצה של כל קישורי השמות התקפים בתוך חלק של תוכנית או בנקודה נתונה בתוכנית, מה שמכונה בצורה נכונה יותר "הקשר" או "סביבה".

במינוח דייקני, ובפועל ברוב שפות התכנות, "חלק מתוכנית" מתייחס לחלק מקוד המקור (אזור הטקסט), והוא ידוע בתור תחום הכרזה לקסיקלי. עם זאת, בשפות מסוימות, "חלק מתוכנית" מתייחס לחלק מזמן הריצה (פרק זמן במהלך ביצוע התוכנית), ומכונה תחום הכרזה דינמי. שני המונחים הללו מטעים במקצת - הם עושים שימוש לרעה במונחים טכניים, כפי שנדון בהגדרה - אבל ההבחנה עצמה מדויקת, ואלה הם המונחים הסטנדרטיים בהתאמה. תחום הכרזה לקסיקלי הוא המוקד העיקרי של מאמר זה, כאשר תחום הכרזה דינמי ניתן להבנה בהנגדה לתחום הכרזה לקסיקלי.

ברוב המקרים, פענוח שמות המבוסס על תחום הכרזה לקסיקלי הוא פשוט יחסית לשימוש ומימוש, שכן בשימוש ניתן לקרוא לאחור בקוד המקור כדי לקבוע לאיזו ישות מתייחס השם, ובמימוש ניתן לשמור רשימה של שמות והקשרים בעת הידור או פירוש של תוכנית. קיימים קשיים במיסוך שמות, הצהרות קדימה והעברת משתנים, בעוד שבעיות עדינות יותר מתעוררות עם משתנים לא מקומיים, במיוחד בסגור.

 הגדרה 
ההגדרה הנוקשה של תחום ההכרזה (הלקסיקלי) של שם (מזהה) היא חד משמעית: היקף לקסיקלי הוא "החלק של קוד המקור שבו חלה קשירה של שם עם ישות". זה נשאר כמעט ללא שינוי מההגדרה משנת 1960 במפרט של ALGOL.

בדרך כלל תחום הכרזה מתייחס למצב שבו שם נתון יכול להתייחס למשתנה נתון - כאשר להצהרה יש השפעה - אך יכול לחול גם על ישויות אחרות, כגון פונקציות, טיפוסים, מחלקות, תוויות, קבועים ומניות (enumerations).

 תחום הכרזה לקסיקלי מול תחום הכרזה דינמי 
הבחנה מהותית בתחום הכרזה היא המשמעות של "חלק מתוכנית". בשפות בעלות תחום הכרזה לקסיקלי (נקרא גם תחום הכרזה סטטי), פענוח השם תלוי במיקום בקוד המקור ובהקשר הלקסיקלי (נקרא גם הקשר סטטי), המוגדר לפי המקום בו המשתנה או הפונקציה מוגדרים. לעומת זאת, בשפות בעלות תחום הכרזה דינמי פענוח השם תלוי במצב התוכנית כאשר נתקלים בשם, אשר נקבע על ידי הקשר הביצוע (נקרא גם הקשר זמן ריצה, הקשר קריאה או הקשר דינמי). בפועל, עם תחום הכרזה לקסיקלי שם מפוענח על ידי חיפוש בהקשר הלקסיקלי המקומי, ואם זה נכשל, על ידי חיפוש בהקשר הלקסיקלי החיצוני לו, וכן הלאה; בעוד שעם תחום הכרזה דינמי, שם מפוענח על ידי חיפוש בהקשר הביצוע המקומי, ואז אם זה נכשל, על ידי חיפוש בהקשר הביצוע החיצוני, וכן הלאה, מתקדמים במעלה מחסנית הקריאות.Borning A. CSE 341 -- Lexical and Dynamic Scoping. University of Washington.

רוב השפות המודרניות משתמשות בתחום הכרזה לקסיקלי למשתנים ופונקציות, אם כי נעשה שימוש בתחום הכרזה דינמי בשפות מסוימות, בעיקר בכמה דיאלקטים של Lisp, כמה שפות "סקריפטים" וכמה שפות תבניות. Perl 5 מציע הן תחום הכרזה לקסיקלי והן דינאמי. אפילו בשפות בעלות תחום הכרזה לקסיקלי, תחום הכרזה עבור סגור יכול לבלבל את חסרי הידע, שכן אלה תלויים בהקשר הלקסיקלי שבו מוגדר הסגור, ולא היכן שהוא נקרא.

ניתן לקבוע פענוח לקסיקלי בזמן הידור, והוא ידוע גם בשם קישור מוקדם (early binding), בעוד שבאופן כללי ניתן לקבוע פענוח דינמי רק בזמן ריצה, ולכן הוא ידוע כקישור מאוחר (late binding).

 מושגים קשורים 
בתכנות מונחה עצמים, שיגור דינמי בוחר מתודה של אובייקט בזמן ריצה, אם כי קישור השם בפועל תלוי בשפה - יש שפות בהן הוא מתבצע בזמן הידור ויש שבזמן ריצה. תחום הכרזה דינמי דה-פקטו נפוץ בשפות מאקרו, שאינן עושות ישירות פענוח שמות, אלא מרחיבות שמות במקום.

כמה שלדי תוכנה, למשל AngularJS, משתמשים במונח "תחום הכרזה" במשמעות שונה לחלוטין מהאופן שבו הוא משמש במאמר זה. בשלדי תוכנה אלו תחום ההכרזה הוא רק אובייקט של שפת התכנות שבה הם משתמשים (JavaScript במקרה של AngularJS) המשמש בדרכים מסוימות את שלד התוכנה כדי לחקות תחום הכרזה דינמי בשפה המשתמשת בתחום הכרזה לקסיקלי עבור המשתנים שלה. תחומי ההכרזה הללו ב-AngularJS יכולים להיות בעצמם בהקשר או לא בהקשר (במשמעות הרגילה של המונח) בכל חלק נתון של התוכנית, בהתאם לכללים הרגילים של תחום הכרזה משתנה של השפה כמו כל אובייקט אחר, ומשתמשים בכללי ירושה והכללה (transclusion) משלהם. בהקשר של AngularJS, לפעמים משתמשים במונח "$scope" (עם סימן דולר) כדי למנוע בלבול, אך השימוש בסימן הדולר בשמות משתנים לרוב מקבל יחס שלילי במדריכי הסגנון.

 שימוש 
תחום הכרזה הוא מרכיב חשוב בפענוח שמות, שהוא בתורו בסיסי לסמנטיקה של השפה. פענוח שמות (כולל תחום הכרזה) משתנה בין שפות תכנות, ובתוך שפת תכנות, משתנה לפי סוג הישות; הכללים לתחום ההכרזה נקראים כללי תחום ההכרזה. יחד עם מרחבי שמות, כללי תחום הכרזה חיוניים בתכנות מודולרי, כך ששינוי בחלק אחד של התוכנית אינו שובר חלק לא קשור.

 סקירה כללית 
כאשר דנים בתחום הכרזה, ישנם שלושה מושגי יסוד: תחום הכרזה (scope), היקף (extent) והקשר (context). לעיתים קרובות מתבלבלים בין "תחום הכרזה" ו"הקשר": תחום הכרזה הוא מאפיין של קישור שם, בעוד שהקשר הוא מאפיין של חלק מתוכנית, כלומר חלק מקוד המקור (הקשר לקסיקלי או הקשר סטטי) או חלק מזמן הריצה (הקשר ביצוע, הקשר זמן ריצה, הקשר קריאה או הקשר דינמי). הקשר ביצוע מורכב מהקשר לקסיקלי (בנקודת הביצוע הנוכחית) בתוספת מצב זמן ריצה כגון מחסנית הקריאות. אם נרצה לדייק, במהלך ריצה, התוכנית נכנסת ויוצאת מתחומי הכרזה שונים של קישורי שמות, ובכל נקודה במהלך הריצה קישורי שמות הם "בהקשר" או "לא בהקשר", ומכאן שקישורי שמות "נכנסים להקשר" או "יוצאים מהקשר" כאשר ריצת התוכנית נכנסת או יוצאת מתחום ההכרזה. עם זאת, בפועל השימוש במונחים הוא הרבה יותר רופף.

תחום הכרזה הוא מושג ברמת קוד מקור, ומאפיין של קישורי שמות, במיוחד קישורי שמות של משתנים או פונקציות - שמות בקוד המקור הם הפניות לישויות בתוכנית - והוא חלק מההתנהגות של מהדר או מפרש של שפה. ככאלה, סוגיות של תחום הכרזה דומות למצביעים, שהם סוג של הפניה המשמשת בתוכניות באופן כללי יותר. שימוש בערך של משתנה כאשר השם נמצא בהקשר אך המשתנה אינו מאותחל הוא אנלוגי לפנייה - גישה לערך - של מצביע פראי, מכיוון שהוא לא מוגדר. עם זאת, מכיוון שמשתנים אינם נהרסים עד שהם יוצאים מהקשרם, המצב האנלוגי למצביע מתנדנד אינו קיים.

עבור ישויות כגון משתנים, תחום הכרזה הוא תת-קבוצה של משך חיים (הידוע גם בשם היקף) - שם יכול להתייחס רק למשתנה שקיים (אף אם ערכו לא מוגדר), אבל משתנים שקיימים אינם בהכרח גלויים: משתנה עשוי להתקיים אך להיות בלתי נגיש (הערך מאוחסן אך לא מתייחס אליו בהקשר נתון), או נגיש אך לא באמצעות השם הפרטי, ובמקרה זה אינו בהקשר (התוכנית "מחוץ לתחום ההכרזה של השם"). במקרים אחרים "אורך חיים" אינו רלוונטי - לתווית (מיקום בעל שם בקוד המקור) יש אורך חיים זהה לתוכנית (עבור שפות הידור סטטי), אך עשוי להיות בהקשר או שלא בנקודה נתונה בתוכנית, וכמו כן עבור משתנים סטטיים - משתנה סטטי גלובלי נמצא בהקשר עבור כל התוכנית, בעוד שמשתנה סטטי מקומי נמצא בהקשר רק בתוך פונקציה או הקשר מקומי אחר, אך לשניהם יש אורך חיים של כל הריצה של התוכנית.

הקביעה לאיזו ישות שם מתייחס ידועה כפענוח שם (name resolution) או קישור שמות (name binding) (במיוחד בתכנות מונחה עצמים), ומשתנה בין השפות. בהינתן שם, השפה (ולמעשה, המהדר או המפרש) בודקת את כל הישויות שנמצאות בהקשר עבור התאמות; במקרה של אי בהירות (שתי ישויות עם אותו שם, כגון משתנה גלובלי ומקומי באותו שם), כללי פענוח השם משמשים להבחין ביניהן. לרוב, פענוח שמות מסתמך על כלל "מההקשר הפנימי לחיצוני", כגון הכלל של Python LEGB (Local, Enclosing, Global, Built-in): שמות מפוענחים, בהיעדר הגדרה מפורשת, להקשר הרלוונטי הצר ביותר. במקרים מסוימים ניתן לציין במפורש את פענוח השם הרצוי, כגון על ידי מילות המפתח global ו-nonlocal ב-Python; במקרים אחרים לא ניתן לעקוף את כללי ברירת המחדל.

כאשר שני שמות זהים נמצאים בהקשר בו-זמנית, ומתייחסים לישויות שונות, אומרים שמתרחש מיסוך שמות, כאשר השם בעל העדיפות הגבוהה יותר (בדרך כלל הפנימי ביותר) "מסווה" את השם בעדיפות נמוכה יותר. ברמת המשתנים, זה ידוע בשם הצללת משתנה. בשל הפוטנציאל לשגיאות לוגיות כתוצאה ממיסוך, שפות מסוימות אוסרות או מונעות מיסוך, ומעלות שגיאה או אזהרה בזמן ההידור או בזמן הריצה.

לשפות תכנות שונות יש כללי תחום הכרזה שונים עבור סוגים שונים של הצהרות ושמות. לכללי תחום הכרזה כאלה יש השפעה רבה על סמנטיקה של השפה, וכתוצאה מכך, על ההתנהגות והנכונות של תוכניות. בשפות כמו C++, לגישה למשתנה לא מקושר אין סמנטיקה מוגדרת היטב ועלולה לגרום להתנהגות לא מוגדרת, בדומה להתייחסות למצביע מתנדנד; והצהרות או שמות משמשים מחוץ לתחום ההכרזה שלהם ייצרו שגיאות תחביר.

תחומי הכרזה קשורים לעיתים קרובות למבני שפה אחרים ונקבעים באופן מרומז, אך שפות רבות מציעות גם מבנים ספציפיים לשליטה בתחום ההכרזה.

 רמות של תחום הכרזה 
לתחום ההכרזה מנעד רחב, החל מביטוי בודד ועד לתוכנית כולה, עם הרבה דרגות אפשריות ביניהם. כלל תחום ההכרזה הפשוט ביותר הוא תחום ההכרזה הגלובלי - כל הישויות זמינות לאורך התוכנית כולה. כלל תחום ההכרזה המודולרי הבסיסי ביותר הוא בעל שתי רמות, עם תחום הכרזה גלובלי בכל מקום בתוכנית, ותחום הכרזה מקומי בתוך פונקציה. תכנות מודולרי מתוחכם יותר מאפשר תחום הכרזה ברמת מודול נפרד, שבו שמות גלויים בתוך המודול (פרטיים למודול) אך אינם גלויים מחוצה לו. בתוך פונקציה, שפות מסוימות, כגון C, מאפשרות תחום הכרזה נפרד לבלוק, ומאפשרות בכך להגביל את תחום ההכרזה גם לאזור מתוך פונקציה; אחרות, בעיקר שפות פונקציונליות, מאפשרות תחום הכרזה לביטוי, כדי להגביל את תחום ההכרזה לביטוי בודד. תחומי הכרזה אחרים כוללים תחום הכרזה ברמת קובץ (בעיקר ב-C) שמתנהג בדומה לתחום הכרזה ברמת מודול, ותחום הכרזה ברמת בלוק גם מחוץ לפונקציות (דוגמה בולטת היא Perl).

סוגיה עדינה היא מתי בדיוק מתחיל ונגמר תחום הכרזה. בשפות מסוימות, כמו C, תחום ההכרזה של השם מתחיל בהכרזת השם, ולכן לשמות שונים המוכרזים בתוך בלוק נתון יכולים להיות תחומי הכרזה שונים. זה מחייב הכרזת פונקציות לפני השימוש בהן, אם כי לא בהכרח הגדרתן, ודורש הכרזה קדימה במקרים מסוימים, במיוחד עבור רקורסיה הדדית. בשפות אחרות, כגון Python, תחום ההכרזה של שם מתחיל בתחילת הבלוק הרלוונטי שבו השם מוכרז (כגון התחלה של פונקציה), ללא קשר למקום שבו הוא מוגדר, כך שלכל השמות בתוך בלוק נתון יש את אותו תחום הכרזה. ב-JavaScript, תחום ההכרזה של שם המוכרז באמצעות let או const מתחיל בהכרזת השם, ותחום ההכרזה של שם המוכרז באמצעות var מתחיל בתחילת הפונקציה שבה השם מוכרז, מנגנון הידוע כהעברת משתנים (variable hoisting). ההתנהגות של שמות בהקשר שיש להם ערך לא מוגדר משתנה בין שפות: ב-Python השימוש בשמות לא מוגדרים מניב שגיאת זמן ריצה, בעוד שב-JavaScript שמות לא מוגדרים המוצהרים עם var ניתנים לשימוש בכל הפונקציה מכיוון שהם קשורים במשתמע לערך undefined.

 תחום הכרזה של ביטוי 
במקרה של תחום הכרזה של ביטוי (expression scope), תחום ההכרזה של קישור השם מוגבל לביטוי בלבד. תחום הכרזה של ביטוי זמין בשפות רבות, במיוחד שפות פונקציונליות המציעות תכונה הנקראת ביטויי הכרזה (let-expressions) המאפשרים לתחום ההכרזה להיות ביטוי יחיד. זה נוח אם, למשל, דרוש ערך ביניים לחישוב. לדוגמה, ב- Standard ML, אם f() מחזירה 12, אז let val x = f() in x * x end הוא ביטוי המוערך ל 144, תוך שימוש במשתנה זמני בשם x כדי להימנע מקריאת f() פעמיים. שפות מסוימות עם תחום הכרזה של בלוק מתקרבות לפונקציונליות זו על ידי מתן תחביר לבלוק שיוטמע בביטוי; לדוגמה, ניתן לכתוב את הביטוי Standard ML שהוזכר לעיל ב- Perl כ- do { my $x = f(); $x * $x }, או ב- GNU C בתור ({ int x = f(); x * x; }) .

ב- Python, למשתני עזר בביטויי מחולל והבנות רשימות (list comprehensions, ב-Python 3) יש תחום הכרזה של ביטוי.

ב-C, לשמות משתנים באב טיפוס של פונקציה יש תחום הכרזה של ביטוי, הידוע בהקשר זה כ- function protocol scope . מכיוון שלא מתייחסים לשמות המשתנים באב הטיפוס (ייתכן שהם שונים בהגדרה בפועל) - הם רק דמה - הם מושמטים לעיתים קרובות, אם כי הם עשויים לשמש ליצירת תיעוד, למשל.

 ראו גם 
 סגור (שפות תכנות)
 משתנה (תכנות)
 Copy constructor

קישורים חיצוניים
 סוגי משתנים, שפת C, מתוך הספר "מיקרו-בקרים ושפה עילית"
 רן בר-זיק, ECMASCRIPT 6 – סקופ נפרד לכל בלוק עם LET, באתר "אינטרנט ישראל"
 מושגים בסיסיים ב-JS: סְקוֹפּ Scope

הערות שוליים

קטגוריה:מבני נתונים