פרק 6: תחביר שפת Smalltalk • יחידות 6.1–6.2 יסודות השפה ואבני הבניין

ביטויים, ליטרלים קבועים וששת הפסאודו-משתנים

התחביר המינימליסטי של Smalltalk: הצהרת משתנים מקומיים ומשותפים, השמות, ליטרלים קבועים, מערכים סטטיים מול דינמיים, Cascade (נקודה-פסיק), וששת הפסאודו-משתנים השמורים בשפה.

🔤
🧠

מודל מנטלי ועיקרון מנחה: תחביר מינימליסטי שבו כל אבן בניין היא עצם חי

💡 האנלוגיה המוחשית

אם שפות כמו Java או ++C הן מפעל עם עשרות מכונות ייעודיות ולוחות בקרה סבוכים, הרי ש-Smalltalk היא ערכת לגו אלגנטית שבה כל החלקים מתחברים בדיוק באותו מנגנון: עצמים המקבלים הודעות.

⚖️ חוק הברזל הפדגוגי

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

🎯 תובנת המפתח

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

1. סעיף 6.1: ממה מורכבים ביטויים (Expressions) ב-Smalltalk?

חמשת היסודות שמרכיבים כל שורת קוד בשפה, משתנים מקומיים מול משותפים, ופעולת ההשמה

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

  • 6 פסאודו-משתנים: true, false, nil, self, super, thisContext.
  • ביטויים קבועים (ליטרלים): מספרים, תווים, מחרוזות, סימבולים ומערכים.
  • הצהרת משתנים, השמות ומשתנים מקומיים.
  • בלוקים ושליחת הודעות.
  • החזרת ערך ממתודה.

בוא נפתח במשתנים ובהשמות. ב-Smalltalk יש חלוקה ברורה ואלגנטית בין שני סוגי משתנים, והאות הראשונה של שם המשתנה מספרת לנו מיד מי הוא:

🔹 משתנים מקומיים (אות ראשונה קטנה)

כוללים משתני מופע, פרמטרים של מתודות ובלוקים, ומשתנים זמניים. לפי המוסכמה, שמות המשתנים המקומיים מתחילים תמיד באות קטנה (כגון x או y). אם השם מורכב מיותר ממילה אחת, הוא יכיל אות גדולה בראש כל מילה החל מהמילה השנייה (CamelCase, למשל savedLink או nextValue).
משתנים זמניים מוצהרים בין שני קווים אנכיים: למשל | x y | מצהיר על שני משתנים מקומיים זמניים.

🔷 משתנים משותפים (אות ראשונה גדולה)

כוללים משתנים גלובליים, משתני מחלקה, pool dictionaries ושמות של מחלקות. לפי המוסכמה, שמות משתנים אלו מתחילים תמיד באות גדולה, וכל מילה נוספת תתחיל גם היא באות גדולה (כגון Object, SmallInteger, OrderedCollection).

⬅️ השמות (Assignments)

השמות למשתנים מתבצעות באמצעות הסימן := או באמצעות חץ שמאלה ← (אשר לעיתים מופיע כקו תחתון _ עקב מגבלות קידוד). לדוגמה: הביטוי x := 1 מבצע השמה של המספר 1 למשתנה x.

🎯 מקבל ההודעה (self)

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

2. ליטרלים קבועים: מספרים, תווים, מחרוזות, סימבולים ומערכים (סטטיים מול דינמיים)

איך מייצגים עצמים קבועים בקוד ומה ההבדל הקריטי בין מערך של זמן הידור למערך של זמן ריצה?

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

🔢 1. מספרים (Numbers) 54 | 2r101 | 54.7 | 2.4e7

מספרים שלמים נכתבים בצורה הרגילה (54). ניתן לכתוב מספרים בבסיסים שונים עם האות r (Radix): למשל 2r101 מייצג את המספר 5 בבסיס 2! מספרים ממשיים מוצגים כרגיל (54.7) או עם חזקה: 2.4e7 השווה ל-$2.4 imes 10^7$.

🔤 2. תווים (Characters) $a | $b | Character tab

נרשמים באמצעות סימן דולר $ לפני התו, למשל $a המייצג את התו 'a'. מופעים של תווים שאינם דפיסים (כמו טאב או רווח) ניתן ליצור באמצעות שליחת הודעות למחלקה Character, למשל ההודעה Character tab.

💬 3. מחרוזות (Strings) 'Hello' | ' ''Donald '

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

🏷️ 4. סימבולים (Symbols) #Hello

מתחילים בסימן סולמית # מלווה באוסף תווים, למשל #Hello. הם דומים למחרוזות, אך ההבדל הקריטי הוא שסימבולים הם ייחודיים במערכת – מובטח שקיים רק מופע אחד ויחיד (Singleton per symbol) עבור כל סימבול!

📦 5. מערכים סטטיים (זמן הידור) #(1 'ab' $a)

מכילים אלמנטים שערכם ידוע בזמן הידור (Compile-time) (מספרים, תווים, מחרוזות, סימבולים ותת-מערכים סטטיים) המופרדים ברווחים בתוך #( ). אין צורך בסימן # פנימי עבור תת-מערכים סטטיים: #(10 #(100 100)) זהה ל-#(10 (100 100)), וביטויים בתוך מערך סטטי אינם מחושבים אלא נספרים כאלמנטים מילוליים (למשל מערך שטוח #(10 100 100) או כתיבת אסימונים מופרדים ברווח ללא חישוב).

⚡ 6. מערכים דינמיים (זמן ריצה) { 1 . 2 . 1+2 }

נרשמים בתוך סוגריים מסולסלים {} ומכילים ביטויים המוערכים בזמן ריצה (Run-time) ומופרדים בנקודות (.)! לדוגמה, { 1 . 2 . 1+2 } מכיל 3 אלמנטים שהם 1, 2 וערך הביטוי (3). כלל קריטי: חובה להשאיר רווח לאחר הנקודה, אחרת המערכת תפרש זאת כמספר עשרוני (כגון 1.2)!

3. מבנים נוספים, סדרת משפטים ומנגנון ה-Cascade (נקודה-פסיק)

הערות, בלוקים, חזרה ממתודה, הפרדת משפטים בנקודה, והסוד של שליחת מספר הודעות לאותו מקבל ב-Cascade

📝 הערות (Comments) ובלוקים (Blocks)

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

↩️ חזרה ממתודה (^) וסדרת משפטים (.)

• חזרה ממתודה: מתבצעת באמצעות חץ כלפי מעלה (^) מלווה בעצם המוחזר. אם מתודה מסתיימת ללא ציון החזרה מפורש, היא מחזירה אוטומטית את self.
• סדרת משפטים: ביטויים המופרדים בנקודה (.). לדוגמה: n := 4. n := n factorial. 'hello' size.

🔗 Cascade (נקודה-פסיק ;): שליחת סדרת הודעות לאותו המקבל!

בניגוד לשפות כמו C או Java שבהן נקודה-פסיק מציינת סוף משפט, ב-Smalltalk סוף משפט מצוין בנקודה (.), ואילו נקודה-פסיק (;) היא אופרטור ה-Cascade! משמעותו של Cascade היא סדרת הודעות המופרדות בנקודה-פסיק ונשלחות כולן ברצף לאותו המקבל המקורי (המקבל נרשם רק פעם אחת בתחילת הביטוי).
לדוגמה, בביטוי 3 + 4 ; + 5:
1. תחילה נשלחת ההודעה + 4 אל העצם המקורי 3 (ומחזירה 7, אך ערך ביניים זה אינו הופך למקבל הבא!).
2. לאחר מכן, בגלל ה-;, ההודעה + 5 נשלחת שוב אל העצם המקורי 3 (ולא אל 7!).
3. הערך המוחזר מהביטוי כולו הוא תמיד תוצאת ההודעה האחרונה בשרשרת, ולכן התוצאה היא 8 (ולא 12)!

4. סעיף 6.2: ששת הפסאודו-משתנים (Pseudo-variables)

למה הם נקראים "פסאודו-משתנים", מי קובע את ערכם, ומה ההבדל המדויק בין self ל-super?

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

1. true ו-false

מופעים יחידים (Singletons) של המחלקות הבוליאניות True ו-False בהתאמה. שים לב לאות הקטנה לעומת שם המחלקה באות גדולה!

2. nil

זהו המופע היחיד (Singleton) של המחלקה UndefinedObject. כל המשתנים במערכת מאותחלים ל-nil כברירת מחדל לפני שבוצעה אליהם השמה.

3. self מול super

שניהם מתייחסים לאותו עצם בדיוק — העצם שקיבל את ההודעה (המקבל של המתודה המתבצעת)! ההבדל הוא שכאשר שולחים הודעה ל-super, איתור המתודה (Method Lookup) משתנה ומתחיל ממחלקת-העל של המחלקה המכילה את המתודה שבה נעשה שימוש ב-super.

4. thisContext

מתייחס למסגרת העליונה (Top Frame) של מחסנית זמן הריצה, כלומר מייצג כעצם חי את הקשר הביצוע הנוכחי של המתודה או הבלוק המתבצעים כעת!

עזר גרפי 6.1 • מנגנון ה-Cascade והשוואת מערכים

כיצד הביטוי 3 + 4 ; + 5 מחזיר 8 (ולא 12), ומה ההבדל בין #(...) ל-{...}?

Cascade & Arrays
תרשים ה-Cascade בביטוי: 3 + 4 ; + 5 3 המקבל המקורי הודעה 1: + 4 מחזירה 7 (נזרק!) הודעה 2 (;): + 5 נשלחת שוב ל-3 ➔ מחזירה 8! הנקודה-פסיק (;) שולחת את כל ההודעות לאותו מקבל מקורי (3)! 📦 מערך סטטי (זמן הידור): #(1 'ab' $a) • מופרד ברווחים, מכיל קבועים הידועים בזמן הידור • אין צורך ב-# פנימי לתת-מערך: #(10 #(100 100)) • ביטויים בתוכו אינם מחושבים אלא נשמרים כאסימונים! ⚡ מערך דינמי (זמן ריצה): { 1 . 2 . 1+2 } • מוקף ב-{}, מכיל ביטויים המופרדים בנקודה (.) • מחושב בזמן ריצה ➔ יכיל את 3 האיברים: 1, 2 ו-3 • חובה להשאיר רווח אחרי הנקודה (אחרת יפורש כ-1.2)!
🔍

מיני-סימולציה 6.1: חוקר ששת הפסאודו-משתנים והליטרלים הקבועים

לחץ על כל פסאודו-משתנה או ליטרל כדי לחקור את התנהגותו בזמן ריצה
🕹️
סימולציה אינטראקטיבית גדולה 6.1

מפענח התחביר החי (Syntax Parser): מערכים סטטיים מול דינמיים, השמות ו-Cascade

בחר ביטוי Smalltalk כדי לראות כיצד המהדר ומערכת זמן הריצה מפרקים כל אסימון (Token), אילו עצמים נוצרים בזיכרון, ומהו הערך המוחזר:

⚡

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

ראינו שכל ביטוי ב-Smalltalk בנוי מעצמים וממשתנים. אבל הקסם האמיתי מתרחש כשאנחנו מתחילים לשלוח הודעות! בלשונית הבאה (סעיף 6.3) נכיר את שלוש תבניות ההודעות (אונרית, בינרית ומילות-מפתח), נבין מדוע הביטוי 3 + 4 * 5 מחזיר ב-Smalltalk דווקא 35 ולא 23, ונבנה יחד סימולטור שפותח שלב-אחר-שלב ביטויים מורכבים כמו 2 raisedTo: 1+3 factorial!

סוף יחידות 6.1–6.2 • ביטויים ופסאודו-משתנים
פרק 6: תחביר שפת Smalltalk • יחידה 6.3 תקשורת בין עצמים וסדר הערכה

שליחת הודעות: אונריות, בינריות ומילות-מפתח, סדר קדימויות ו-Cascade

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

✉️
🧠

מודל מנטלי ועיקרון מנחה: פירמידת הקדימויות (אונרי ← בינרי ← מילות-מפתח) והערכה משמאל לימין

💡 האנלוגיה המוחשית

במקום טבלת קדימויות של 15 רמות אופרטורים כמו ב-++C, ב-Smalltalk יש רק 3 הילוכים של הודעות: הילוך ראשון ומהיר ביותר להודעות אונריות (ללא ארגומנטים), הילוך שני לבינריות (תווים מיוחדים), והילוך שלישי למילות-מפתח (עם נקודתיים).

⚖️ חוק הברזל הפדגוגי

אין קדימות לכפל על פני חיבור! מכיוון שגם + וגם * הן הודעות בינריות בעלות אותה דרגת קדימות בדיוק, הן מוערכות תמיד משמאל לימין: 3 + 4 * 5 = 35 (וכדי לקבל 23 חובה לכתוב 3 + (4 * 5)).

🎯 תובנת המפתח

כל מילות המפתח המופיעות ברצף ללא סוגריים מתחברות להודעה אחת גדולה (word1:word2:word3:). כדי לשלוח הודעת מילת-מפתח פנימית כארגומנט להודעה חיצונית, חובה לתחום אותה בסוגריים!

1. שלוש תבניות ההודעות ב-Smalltalk

איך מבחינים במבט אחד בין הודעה אונרית, בינרית והודעת מילות-מפתח?

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

קדימות 1 (הגבוהה ביותר) 0 ארגומנטים

1. הודעה אונרית (Unary)

מורכבת ממילה אחת בלבד וללא ארגומנטים (וללא נקודתיים). מקובל להתחיל אותה באות קטנה.
התחביר: receiver selector.

3 factorial   "➔ 6"
'hello' size  "➔ 5"
קדימות 2 (בינונית) ארגומנט 1 תמיד

2. הודעה בינרית (Binary)

מורכבת מתו אחד או יותר מתוך האוסף המוגדר: + - * / = > | < & ~ @ % ? ,. מקבלת תמיד ארגומנט אחד בדיוק!
התחביר: receiver selector argument.

1 + 2       "➔ 3"
10 @ 20     "➔ Point(10,20)"
קדימות 3 (הנמוכה ביותר) 1+ ארגומנטים

3. מילות-מפתח (Keyword)

מורכבת ממילת מפתח אחת או יותר המסתיימות בנקודתיים (:). כל מילת מפתח מלווה בארגומנט.
התחביר: receiver word1: arg1 word2: arg2.

anArray at: 2 put: 10
2 raisedTo: 7  "➔ 128"

2. דרגת קדימות וסדר הערכה של ביטויים מורכבים

ארבעת כללי הברזל של סדר ההערכה והמלכודת המתמטית של אופרטורים בינריים

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

  1. סוגריים עגולים ( ) – משנים את סדר ההערכה ומתבצעים תמיד ראשונים.
  2. הודעות אונריות (Unary) – דרגת הקדימות הגבוהה ביותר מבין ההודעות.
  3. הודעות בינריות (Binary) – דרגת קדימות שנייה (כולן שוות זו לזו ומוערכות משמאל לימין!).
  4. הודעות מילות-מפתח (Keyword) – דרגת הקדימות הנמוכה ביותר.

🔍 דוגמה 1: ניתוח 2 raisedTo: 1+3 factorial

  • שלב 1 (אונרית): ההודעה האונרית factorial נשלחת לעצם 3 ומחזירה 6.
  • שלב 2 (בינרית): ההודעה הבינרית + נשלחת לעצם 1 עם הארגומנט 6 ומחזירה 7.
  • שלב 3 (מילת-מפתח): הודעת מילות-המפתח raisedTo: נשלחת לעצם 2 עם הארגומנט 7, ומחזירה 128 ($2^7$).
  • הוספת סוגריים תשנה את התוצאה: בביטוי (2 raisedTo: 1) + 3 factorial הסוגריים מחשבים תחילה 2 raisedTo: 1 (שמחזיר 2), האונרית 3 factorial מחזירה 6, והחיבור 2 + 6 מחזיר 8!

⚠️ הערה קריטית: מלכודת 3 + 4 * 5

סדר הקדימות של אופרטורים מתמטיים ב-Smalltalk הוא משמאל לימין בהיותם הודעות בינריות זהות בעדיפותן, ולא על פי כללי המתמטיקה המקובלים!
• לכן, הביטוי 3 + 4 * 5 יוערך תחילה כ-(3 + 4) שמחזיר 7, ואז 7 * 5 שיחזיר את העצם 35!
• כדי לקבל את התוצאה המתמטית 23, חובה להשתמש בסוגריים: 3 + (4 * 5).

3. הרכבת הודעות מורכבות (Cascade מול סדרת ביטויים) וזיהוי הודעות מילות-מפתח

איך פועלים ביטויי Cascade מתקדמים ואיך שוברים רצף של מילות-מפתח בעזרת סוגריים?

🔗 ניתוח שרשורי Cascade מול סדרת ביטויים

• דוגמה א' (2 raisedTo: 2; * 3 factorial): תחילה ההודעה raisedTo: 2 נשלחת ל-2 (ומחזירה 4). לאחר מכן ההודעה האונרית factorial נשלחת ל-3 (ומחזירה 6). לבסוף, בגלל ה-Cascade (;), ההודעה * 6 נשלחת אל המקבל המקורי של ה-Cascade (שהוא 2, ולא 4!), ולכן המערכת מחזירה 12!
• דוגמה ב' (10@10 - 10 ; corner: 30@30 ; isZero): ההודעה הבינרית @ יוצרת את הנקודה 10@10 שהיא המקבל של שלוש ההודעות הבאות: הראשונה - 10 מחזירה 0@0; השנייה corner: 30@30 נשלחת שוב ל-10@10; והשלישית isZero נשלחת שוב ל-10@10 ומחזירה false (התוצאה הסופית!).
• לעומת זאת, סדרת ביטויים בנקודה (.): מעריכה כל משפט בנפרד. בביטוי 2 raisedTo: 2+2. 2*3 factorial raisedTo: 2. הביטוי הראשון מחזיר 16 ($2^4$), והביטוי השני מחשב 3 factorial (6), כופל 2 * 6 (12), ומעלה בריבוע 12 raisedTo: 2 שמחזיר 144!

🧩 זיהוי הודעות מילות-מפתח ושבירה בסוגריים

הודעת מילת-מפתח מורכבת מצירוף כל מילות המפתח ברצף:
• הביטוי x word1: arg1 word2: arg2 word3: arg3 שולח הודעה אחת שלמה בשם word1:word2:word3: אל העצם x.
• כדי לשבור את הרצף ולשלוח הודעות שונות זו אחר זו, משתמשים בסוגריים: (x word1: arg1 word2: arg2) word3: arg3 ישלח תחילה את word1:word2: אל x, ואז ישלח את word3: אל התוצאה שהוחזרה!
• דוגמה מעשית חיונית: בביטוי st at: (st indexOf: $e) put: $x הסוגריים מפעילים תחילה את ההודעה indexOf: $e על המחרוזת st, ואת המיקום שהוחזר מעבירים כארגומנט למילת המפתח at: בתוך ההודעה הגדולה at:put:! (ללא סוגריים המהדר היה מחפש מתודה לא קיימת בשם at:indexOf:put:!).

עזר גרפי 6.2 • עץ הערכה לפי דרגות קדימות

פירוק שלבי ההערכה של 2 raisedTo: 1 + 3 factorial (אונרי ← בינרי ← מילת-מפתח)

Evaluation Tree
שלב 1: הודעה אונרית (ירוק) 3 factorial ➔ 6 העצם 3 מקבל את ההודעה האונרית factorial ומחזיר את העצם 6. 2 raisedTo: 1 + 6 ⟵ שלב 2: הודעה בינרית (כחול) 1 + 6 ➔ 7 העצם 1 מקבל את ההודעה הבינרית + עם הארגומנט 6 ומחזיר 7. 2 raisedTo: 7 ⟵ שלב 3: מילת-מפתח (אדום) 2 raisedTo: 7 ➔ 128 העצם 2 מקבל את הודעת מילת-המפתח raisedTo: עם 7 ומחזיר 128! תוצאה סופית: 128
🔬

מיני-סימולציה 6.2: מפענח הודעות מילות-מפתח וסוגריים

לחץ על כל ביטוי כדי לראות איזה Selector בדיוק נשלח לאיזה עצם
🧮
סימולציה אינטראקטיבית גדולה 6.2

מנתח סדר הערכה (Evaluation Visualizer): צעד-אחר-צעד בצבעים!

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

שלב 1 מתוך 3
⚡

הגשר הנרטיבי: הבנו איך שולחים הודעות – אבל איפה מוגדרות המתודות שמגיבות להן, ומהם בלוקים של קוד?

כשעצם מקבל הודעה (כמו add: link after: otherLink), מופעלת המתודה המתאימה במחלקה שלו. בלשונית הבאה (סעיפים 6.4–6.5) נלמד את ארבעת חלקי המתודה וננתח שורה-אחר-שורה מתודה אמיתית מתוך רשימה מקושרת (LinkedList). מיד לאחר מכן נכיר את אחד הכלים החזקים ביותר ב-Smalltalk: בלוקים ([...]) — פונקציות אנונימיות שאפשר לארוז כעצמים, להעביר להן פרמטרים, ללכוד בהן משתנים מהסביבה החיצונית (Closure), ואפילו לצאת דרכן מהמתודה כולה בעזרת ^!

פרק 6: תחביר שפת Smalltalk • יחידות 6.4–6.5 הגדרת פעולות ופונקציות אנונימיות

מתודות (Methods) ובלוקים של קוד (Blocks & Closures)

ארבעת חלקי המתודה, ניתוח מלא של המתודה add:after: במחלקה LinkedList, בלוקים כמופעים של BlockContext, פרמטרים ומשתנים מקומיים בבלוק, הכרה סביבתית (Closure) ויציאה מלאה ממתודה (^).

📦
🧠

מודל מנטלי ועיקרון מנחה: הקפאת קוד בתוך עצם (Block) והכוח של Non-Local Return

💡 האנלוגיה המוחשית

מתודה היא מתכון קבוע שרשום בספר המתכונים של המחלקה; בלוק ([...]) הוא "פתק הוראות נייד" שארוז כעצם עצמאי — אפשר למסור אותו לעצם אחר, והוא יופעל רק כשישלחו לו את ההודעה value!

⚖️ חוק הברזל הפדגוגי

הבדל קריטי בערך ההחזרה: מתודה ללא ^ מחזירה אוטומטית את self. בלוק ללא ^ מחזיר את תוצאת הביטוי האחרון בבלוק. ואם בלוק כן מכיל ^ — הוא מבצע יציאה מלאה מהמתודה העוטפת כולה!

🎯 תובנת המפתח

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

1. סעיף 6.4: מבנה המתודה ב-Smalltalk וניתוח add: link after: otherLink

ארבעת החלקים הקבועים של כל מתודה וניתוח מעשי של חיבור חוליה ברשימה מקושרת (LinkedList)

מתודות משויכות למחלקות ומוגדרות לרוב בעזרת ה-System Browser. התחביר להגדרת מתודה ב-Smalltalk בנוי מארבעה חלקים עיקריים וברורים:

1. messageSelectorAndArgumentNames   ← תבנית המתודה (Method Pattern), למשל: at: index put: anInteger
2. "comment stating purpose of message" ← הערה במרכאות כפולות המבהירה את מטרת המתודה
3. | temporary variable names |         ← הצהרת משתנים מקומיים זמניים בין קווים אנכיים
4. statements                           ← החלק הביצועי: ביטויים מופרדים בנקודה, חזרה עם ^ (או ברירת מחדל self)

בוא ננתח יחד דוגמה אמיתית וחשובה מתוך המחלקה LinkedList (רשימה מקושרת): המתודה add: link after: otherLink, שתפקידה לחבר צומת חדש (link) מיד אחרי צומת קיים (otherLink) ברשימה:

add: link after: otherLink תבנית המתודה: מקבלת את הצמתים link ו-otherLink (מתחילים באות קטנה)
"Add otherLink after link in the list. Answer aLink." הערת תיעוד במרכאות כפולות
| savedLink | משתנה מקומי לשמירת המצביע המקורי של הצומת הבא
otherLink == lastLink ifTrue: [^ self addLast: link]. בדיקת קצה: אם otherLink הוא האחרון, שלח addLast: וצא מוקדם מהמתודה!
savedLink := otherLink nextLink. קריאת המצביע הבא באמצעות ההודעה nextLink ושמירתו ב-savedLink
otherLink nextLink: link. עדכון otherLink כך שיצביע אל הצומת החדש link
link nextLink: savedLink. עדכון הצומת החדש link כך שיצביע אל המשך הרשימה (savedLink)
^link. החזרה מפורשת של הצומת link שצורף לרשימה

2. סעיף 6.5: בלוקים (Blocks) – פונקציות אנונימיות, פרמטרים, Closure ויציאה ממתודה

איך בלוקים שומרים קוד להערכה נדחית, איך מעבירים להם ארגומנטים, ומה קורה כשכותבים ^ בתוך בלוק?

מהו בלוק? בלוק הוא סדרה של ביטויים הכלואים בתוך סוגריים מרובעים [...]. למעשה, בלוק הוא עצם לכל דבר (מופע של המחלקה BlockContext או BlockClosure) המייצג פונקציה אנונימית (חסרת שם). הקוד שבתוך הבלוק אינו מתבצע בעת הגדרת הבלוק, אלא מוערך אך ורק כאשר שולחים לבלוק את ההודעה value!

לדוגמה, שליחת ההודעה value לבלוק [2 raisedTo: 1+3 factorial] value תחשב את הביטוי שבתוכו ותחזיר את הערך 128.

🚨 הבחנה קריטית: סיום רגיל של בלוק לעומת חזרה מפורשת (^) בתוך בלוק!

• סיום רגיל (ללא ^): אם הבלוק מגיע לסיומו הרגיל, הוא מחזיר לשולח ההודעה value את ערכו של הביטוי האחרון בבלוק, והמתודה החיצונית ממשיכה לרוץ כרגיל.
• חזרה מפורשת עם ^ בתוך בלוק (Non-local return): אם הבלוק מכיל הוראת חזרה מפורשת באמצעות הסימן ^, ביצוע הבלוק יסתיים מיד ותתבצע יציאה מלאה מהמתודה כולה שבה נכתב הבלוק! (בדיוק כפי שראינו בשורה otherLink == lastLink ifTrue: [^ self addLast: link] במתודה של LinkedList!).

📥 פרמטרים של בלוק (value: ועד valueWithArguments:)

פרמטרים נרשמים בתחילת הבלוק, כשנקודתיים (:) מקדימות כל שם פרמטר, וקו אנכי (|) מפריד בינם לבין גוף הבלוק:
• פרמטר אחד: [:x | 2 raisedTo: x+3 factorial] value: 2 מחזיר 256 ($2^{2+6} = 2^8$).
• שני פרמטרים: משתמשים בהודעה כפולה value:value:, לדוגמה: [:x :y | x raisedTo: 1+y factorial] value: 3 value: 2 מחזיר 27 ($3^{1+2} = 3^3$).
• מעל 4 פרמטרים: ניתן להעביר עד 4 פרמטרים עם משפחת value:.... עבור מעל 4 פרמטרים, משתמשים בהודעה valueWithArguments: המקבלת מערך של ערכים!

🔒 משתנים מקומיים בבלוק והכרה סביבתית (Closure)

• משתנים מקומיים בבלוק: נרשמים בין קווים אנכיים |...| מיד לאחר הפרמטרים (אם ישנם). לדוגמה:
[:x :y | | n | n := x raisedTo: y. n := n + (y raisedTo: x). ] value: 2 value: 3 מחשב $n := 2^3 = 8$, מוסיף $3^2 = 9$, ומחזיר 17!
• הכרה סביבתית (Closure): בלוקים יכולים לגשת למשתנים שהוגדרו מחוץ לבלוק בהקשר שעוטף אותם! לדוגמה, אם נגדיר בחוץ y := 2, אזי הבלוק [:x | x raisedTo: y] value: 5 ניגש ל-y החיצוני ומחזיר 25 ($5^2$).

עזר גרפי 6.3 • חיבור חוליה ב-LinkedList ואנטומיית הבלוק

ניתוח המצביעים ב-add: link after: otherLink ומבנה הבלוק

Methods & Blocks
ניתוח המצביעים במתודה: add: link after: otherLink otherLink savedLink link (חדש) 1. savedLink := otherLink nextLink 2. nextLink: link 3. nextLink: savedLink אנטומיה מלאה של בלוק (BlockContext) [:x :y | | n | n := x + y. n * extVar ] • :x :y ⟵ פרמטרים של הבלוק (לפני הקו האנכי |) • | n | ⟵ משתנים מקומיים של הבלוק • extVar ⟵ הכרה סביבתית (Closure) של משתנה חיצוני • ^ בבלוק ⟵ יוצא מיד מהמתודה העוטפת כולה! • מוערך ע"י: value / value: / valueWithArguments:
🔗

מיני-סימולציה 6.3: סימולטור שורה-אחר-שורה של המתודה add: link after: otherLink

לחץ על כל שלב כדי לעקוב אחר המצביעים ברשימה המקושרת
📦
סימולציה אינטראקטיבית גדולה 6.3

מעבדת הבלוקים וטווח ההכרה (Block Scope, Closure & Non-Local Return)

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

⚡

הגשר הנרטיבי: עכשיו כשאנחנו יודעים לארוז קוד בתוך בלוקים – איך מממשים תנאים ולולאות בלי מילות המפתח if, for ו-while?

בשפות רגילות, if ו-while הם פקודות תחביריות קשיחות של המהדר. אבל ב-Smalltalk יש לנו בלוקים שמאפשרים דחיית ביצוע (Deferred Evaluation)! בלשונית הבאה (סעיפים 6.6.1–6.6.2) נראה את אחד הרעיונות היפים ביותר במדעי המחשב: איך תנאים ממומשים כפולימורפיזם טהור בין המחלקות True ו-False, מה ההבדל בין אופרטורים לוגיים עצלים (and: / or:) למלאים (& / |), ואיך לולאות whileTrue:, timesRepeat: ו-to:do: בנויות כולן משליחת הודעות רגילה!

פרק 6: תחביר שפת Smalltalk • יחידות 6.6.1–6.6.2 מבני בקרה באמצעות הודעות ובלוקים

תנאים פולימורפיים (Conditionals) ולולאות בסיסיות (Loops)

היעדר מילות מפתח תחביריות ב-Smalltalk, מימוש פולימורפי של ifTrue:ifFalse: במחלקות True ו-False, אופרטורים לוגיים עצלים מול מלאים, ולולאות whileTrue:, timesRepeat: ו-to:do:.

🔀
🧠

מודל מנטלי ועיקרון מנחה: פולימורפיזם טהור ודחיית ביצוע במקום מילות מפתח שמורות

💡 האנלוגיה המוחשית

במקום שוטר תנועה חיצוני (פקודת if של השפה) שבודק האם הרמזור ירוק או אדום, ב-Smalltalk העצמים true ו-false הם שני פקידים חכמים: true מפעיל תמיד רק את המעטפה הראשונה (trueAlternativeBlock value), ו-false מפעיל תמיד רק את השנייה!

⚖️ חוק הברזל הפדגוגי

בלולאת whileTrue:, המקבל חייב להיות בלוק ללא פרמטרים ([ n < 10 ]) ולא ערך בוליאני מחושב, כדי שהתנאי יוערך מחדש (self value) בכל איטרציה!

🎯 תובנת המפתח

הבנת ההבדל בין סוגריים לאינטרוולים: 1 to: 10 do: [...] שולח הודעה אחת to:do: למספר 1, ואילו (1 to: 10) do: [...] יוצר תחילה עצם מסוג Interval ושולח אליו את הודעת האוסף do:!

1. סעיף 6.6.1: תנאים (Conditionals) – פולימורפיזם טהור בין True ו-False

איך מבצעים משפט תנאי ללא מילת מפתח if, מה מוחזר במקרה של אי-התאמה, ואופרטורים לוגיים עצלים מול מלאים

בשפות תכנות סטנדרטיות (כמו Java או C) קיימות מילות מפתח שמורות עבור מבני בקרה (if, else, while, for). בניגוד מוחלט אליהן, בשפת Smalltalk אין שום תחביר ייעודי או מילות מפתח עבור תנאים ולולאות! כל ביצוע של משפט תנאי נעשה אך ורק באמצעות שליחת הודעות לעצמים בוליאניים, וכל ביצוע של לולאה נעשה באמצעות שליחת הודעות לבלוקים, למספרים שלמים או לאוספים. הודעות אלו מקבלות בלוקים של קוד כארגומנטים, וכך מיושמת דחיית הביצוע (Deferred Evaluation) עד לבדיקת התנאים!

ביצוע תנאי מבוסס על שליחת אחת מארבע הודעות מילת-המפתח הבאות אל עצם בוליאני (המחזיר true או false): ifTrue:, ifFalse:, ifTrue:ifFalse:, ifFalse:ifTrue:.

🔍 דוגמת קוד בסיסית וניתוח הביצוע

| x y |
x := 3.
y := 2.
(x > y)
    ifTrue: [ x raisedTo: y ]
    ifFalse: [ y raisedTo: x ]
  1. הביטוי x > y שולח ל-x (3) את ההודעה הבינרית > עם הארגומנט y (2).
  2. המספר 3 מבצע את ההשוואה ומחזיר את העצם true (מופע יחיד של המחלקה True).
  3. לעצם true נשלחת ההודעה ifTrue:ifFalse: עם שני הבלוקים כארגומנטים.
  4. התוצאה המוחזרת היא ערך הבלוק הראשון: $3^2 = 9$.

💎 המימוש הפולימורפי במחלקות True ו-False

כיצד זה עובד ללא פקודת if חומרתית? באמצעות פולימורפיזם טהור!

"במחלקה True - מפעילה רק את הבלוק הראשון:"
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
    ^ trueAlternativeBlock value
"במחלקה False - מפעילה רק את הבלוק השני:"
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
    ^ falseAlternativeBlock value

⚠️ התנהגות באי-התאמה: שליחת ifTrue: לעצם false אינה מבצעת את הבלוק ומחזירה nil! ובאופן דומה, שליחת ifFalse: לעצם true אינה מבצעת את הבלוק ומחזירה nil.

⚡ אופרטורים לוגיים: עצלים (Short-circuit: and:, or:) מול מלאים (Eager: &, |) ושלילה (not)

1. אופרטורים עצלים (and:, or:) מקבלים בלוק כארגומנט: expr1 and: [ expr2 ] או expr1 or: [ expr2 ]. הבלוק מוערך (value) אך ורק במקרה הצורך: ב-and: אם המקבל הוא false, הבלוק השני כלל לא מוערך ומוחזר מיד false (self). ב-or: אם המקבל הוא true, הבלוק השני לא מוערך ומוחזר מיד true.
2. אופרטורים מלאים (&, |) הודעות בינריות המקבלות ערך בוליאני שכבר חושב מראש כארגומנט: expr1 & expr2. שני האגפים מחושבים תמיד בהתאם לחוקי הקדימות, ללא תלות בתוצאת האגף הראשון!
3. שלילה אונרית (not) הודעה אונרית פולימורפית: במחלקה True מחזירה false, ובמחלקה False מחזירה true.

2. סעיף 6.6.2: שלוש הלולאות הבסיסיות (whileTrue:, timesRepeat:, to:do:)

למה whileTrue: נשלחת לבלוק, איך ממומשות timesRepeat: ו-to:do: מאחורי הקלעים, ומה ההבדל הקריטי בסוגריים סביב אינטרוול?

1. לולאת while (תנאי דינמי)

נשלחת אל בלוק ללא פרמטרים!

עקרון קריטי: בלולאת while ההודעה נשלחת אל בלוק (ולא אל ערך בוליאני ישיר), משום שהתנאי חייב להיות מחושב מחדש בכל איטרציה! הבלוק המקבל חייב להיות ללא פרמטרים ולהחזיר בוליאני.
• בדוגמה n := 1. [ n < 10 ] whileTrue: [ n := n * 2 ]. ערכו הסופי של n יהיה 16.
• קיימות גם whileFalse: וצורות אונריות whileTrue ו-whileFalse ללא בלוק ארגומנט.

"במחלקה BlockContext / BlockClosure:"
whileTrue: aBlock
    ^ [ self value ] whileTrue: [ aBlock value ]
2. חזרה קבועה (timesRepeat:)

נשלחת אל עצם מסוג Integer

מיועדת לביצוע מספר מוגדר וידוע מראש של איטרציות.
• בדוגמה n := 1. 10 timesRepeat: [ n := n * 2 ]. הבלוק ירוץ 10 פעמים וערכו של n בסיום יהיה 1024 ($2^{10}$).
• תובנה מבנית: המתודה timesRepeat: ממומשת בעצמה באמצעות whileTrue: ומשתנה מקומי count הרץ מ-1 עד self!

"במחלקה Integer:"
timesRepeat: aBlock
    | count |
    count := 1.
    [ count <= self ] whileTrue: [
        aBlock value.
        count := count + 1 ]
3. לולאת מונה (to:do: / to:by:do:)

נשלחת אל עצם מסוג Number

מקבילה ללולאת for: מקדמת מונה מערך התחלתי (self) עד stop ומפעילה את הבלוק עם המונה התורן (aBlock value: nextValue).
• בדוגמה n := 0. 1 to: 10 do: [ :counter | n := n + counter ]. ערכו של n בסיום יהיה 55.
• לקפיצות מותאמות משתמשים ב-to:by:do: (חיובי או שלילי, כגון 0 to: 9 by: 2 do: [ :i | ... ]).

"במחלקה Number:"
to: stop do: aBlock
    | nextValue |
    nextValue := self.
    [ nextValue <= stop ] whileTrue: [
        aBlock value: nextValue.
        nextValue := nextValue + 1 ]

⚠️ משמעות קריטית של סוגריים מול אינטרוולים (Interval):

• עם סוגריים: הביטוי (1 to: 10) do: [ :element | ... ] שולח תחילה למספר 1 את ההודעה to: 10 שיוצרת עצם מסוג Interval, ואז שולח אל ה-Interval את הודעת האוסף do:!
• ללא סוגריים: הביטוי 1 to: 10 do: [ :counter | ... ] מפורש כהודעת מילת-מפתח אחת to:do: הנשלחת ישירות למספר 1 (ללא יצירת עצם Interval כלל!).
• (ואילו כתבת בטעות ללא סוגריים 1 to: 10 collect: [...], המהדר היה מחפש מתודה לא קיימת בשם to:collect: במחלקה Number — לכן באיטרטורים של אוספים חובה לכתוב סוגריים סביב (1 to: 10)!).

עזר גרפי 6.4 • תנאים פולימורפיים ושרשרת הלולאות

ניתוב פולימורפי ב-True ו-False והבנייה ההיררכית של הלולאות מעל whileTrue:

Polymorphic Control Flow
המימוש הפולימורפי של ifTrue:ifFalse: מחלקה True (מקבל: true) ^ trueBlock value מפעילה רק את הבלוק הראשון ומתעלמת מהבלוק השני! true ifFalse: [...] ➔ nil מחלקה False (מקבל: false) ^ falseBlock value מפעילה רק את הבלוק השני ומתעלמת מהבלוק הראשון! false ifTrue: [...] ➔ nil הבנייה של הלולאות ב-Smalltalk Integer >> timesRepeat: מונה count מ-1 עד self Number >> to:do: מונה nextValue מ-self עד stop BlockClosure >> whileTrue: aBlock שתיהן ממומשות פנימית באמצעות [ ... ] whileTrue: [ ... ]!
⚡

מיני-סימולציה 6.4: אופרטורים עצלים (and:/or:) מול מלאים (&/|) והחזרת nil

לחץ על כל ביטוי כדי לבדוק האם האגף השני מחושב ומה מוחזר
🔄
סימולציה אינטראקטיבית גדולה 6.4

מעבדת הלולאות והתנאים הפולימורפיים: מעקב איטרציה-אחר-איטרציה!

בחר מנגנון לולאה או תנאי וצפה בטבלת המעקב המלאה של כל איטרציה, כולל הקוד הפנימי שרץ מאחורי הקלעים במחלקות True/False, BlockClosure, Integer ו-Number:

⚡

הגשר הנרטיבי: מלולאות בסיסיות לאיטרטורים פונקציונליים על אוספים ולפרימיטיבים של המכונה הווירטואלית!

ראינו כיצד whileTrue:, timesRepeat: ו-to:do: פועלות. אבל הכוח האמיתי של Smalltalk טמון באיטרטורים הפונקציונליים על אוספים! בלשונית הבאה נכיר את ששת האיטרטורים המרכזיים (do:, collect: ומנגנון ה-species, select:, reject:, detect:ifNone:, ו-inject:into: כולל מימוש occurrencesOf:), נחשוף את המלכודת הקלאסית של מחיקת איברים תוך כדי איטרציה, ונחתום את הפרק עם סעיף 6.7: פרימיטיבים (<primitive: 1>) ומנגנון הנסיגה (Fallback Code) במכונה הווירטואלית!

פרק 6: תחביר שפת Smalltalk • יחידות 6.6.2–6.7 איטרציות פונקציונליות ופקודות המכונה הווירטואלית

איטרטורים על אוספים (Collection Iterations) ופרימיטיבים (Primitives)

ששת האיטרטורים הפונקציונליים (do:, collect: ו-species, select:, reject:, detect:ifNone:, inject:into:), מלכודת שינוי אוסף תוך כדי איטרציה, ופרימיטיבים (<primitive: 1>) ומנגנון הנסיגה (Fallback Code) ב-VM.

🔄
🧠

מודל מנטלי ועיקרון מנחה: פס ייצור פונקציונלי לאוספים וגשר יעיל אל המכונה הווירטואלית

💡 האנלוגיה המוחשית

במקום לכתוב לולאת אינדקסים ידנית לכל פעולה על אוסף, האוסף עצמו מציע לנו "תחנות עיבוד": select: היא מסננת, collect: היא פס התמרה שמייצר אוסף חדש (בעזרת species), ו-inject:into: היא מכונת צבירה וקיפול!

⚖️ חוק הברזל הפדגוגי

לעולם אל תמחק איברים מאוסף תוך כדי ריצת do: עליו! מחיקה מסיטה את האינדקסים וגורמת לדילוג על איברים עוקבים. הפתרון: איטרציה על עותק (range copy do:) או שימוש ב-select: / reject:.

🎯 תובנת המפתח

פרימיטיב (<primitive: 1>) מבצע פעולת יסוד ישירות במכונה הווירטואלית (VM). אם הוא מצליח הערך מוחזר מיד, ואם הוא נכשל (למשל גלישת שלם או חיבור עם Float) — מורץ קוד ה-Fallback ב-Smalltalk שמתחתיו!

1. סעיף 6.6.2 (חלק ד'): איטרציות פונקציונליות על אוספים (Collection Iterations)

שש מתודות האיטרציה המרכזיות הנשלחות אל אוספים (Interval, Array, OrderedCollection, Set, String)

ב-Smalltalk אנחנו כמעט לעולם לא כותבים לולאות אינדקסים ידניות כדי לעבד אוספים. במקום זאת, מחלקות האוספים (כגון Interval, Array, OrderedCollection, Set ו-String) מציעות לנו שש מתודות איטרציה פונקציונליות המקבלות בלוק:

1. do: ו-2. collect: (מקבילה ל-Map + מנגנון ה-species)

• do:: מעבר סדרתי על כל איברי האוסף והפעלת הבלוק עליהם, ללא יצירת אוסף חדש (למשל (1 to: 10) do: [:element | ...]).
• collect:: מפעילה את הבלוק על כל איבר באוסף ומחזירה אוסף חדש המורכב מהערכים שהוחזרו מהבלוק!
דוגמה על תחום: (1 to: 10) collect: [ :element | element + 1 ] מחזיר את המערך: #(2 3 4 5 6 7 8 9 10 11).
דוגמה על מחרוזת: 'abcdefghijk' collect: [:char | char asUppercase] מחזיר את המחרוזת החדשה 'ABCDEFGHIJK'.

"מימוש collect: במחלקה Interval ומנגנון ה-species:"
collect: aBlock
    | nextValue result |
    result := self species new: self size.
    nextValue := start.
    1 to: result size do: [ :i |
        result at: i put: (aBlock value: nextValue).
        nextValue := nextValue + step ].
    ^ result

למה self species? משום שתוצאת הטרנספורמציה של collect: על Interval אינה בהכרח סדרה חשבונית! לכן ההודעה species במחלקה Interval מחזירה את המחלקה Array, ויוצרת מערך חדש בגודל המתאים (new: self size).

3. select:, 4. reject: ו-5. detect: / detect:ifNone:

• select: (מקבילה ל-Filter): מחזירה אוסף חדש הכולל רק את האיברים שעבורם הבלוק החזיר true:
#(1 2 3 4 5 6 7 8 9 0) select: [ :each | each even ] "➔ #(2 4 6 8 0)" (1 to: 10) select: [:element | element > 5] "➔ #(6 7 8 9 10)" • reject: (שלילת סינון): מחזירה אוסף חדש הכולל רק את האיברים שעבורם הבלוק החזיר false:
#( 'This' 'is' 'an' 'Example') reject: [ :each | (each at: 1) isLowercase ] "➔ #('This' 'Example')"

"מימוש reject: במחלקה Collection באמצעות select::"
reject: aBlock
    ^ self select: [ :element | (aBlock value: element) == false ]

• detect: ו-detect:ifNone:: מחזירה את האיבר הראשון באוסף שעבורו הבלוק מחזיר true:
(1 to: 10) detect: [ :element | element > 5 ] "➔ מחזיר 6" אם לא נמצא איבר מתאים, detect: מעוררת חריגה (Exception) מסוג NotFound. כדי למנוע קריסה משתמשים ב-detect:ifNone: המספקת בלוק חלופי שיופעל במקרה של כישלון!

6. inject:into: (מקבילה ל-Reduce / Fold) ומימוש occurrencesOf:

מבצעת צבירה וקיפול של איברי האוסף. מקבלת ארגומנט ראשון שהוא הערך ההתחלתי של המצבר, ובלוק בעל שני פרמטרים: המצבר (subTotal / acc / sum) והאיבר התורן (next / each). ערך ההחזרה מהבלוק הופך למצבר בשלב הבא:
(1 to: 10) inject: 0 into: [ :subTotal :next | subTotal + next ] "➔ 55"

"דוגמה קלאסית: מימוש occurrencesOf: באמצעות inject:into::"
occurrencesOf: anObject
    ^ self inject: 0
        into: [ :acc :each |
            (each = anObject)
                ifTrue: [ acc + 1 ]
                ifFalse: [ acc ] ]

🚨 מלכודת ידועה: שינוי אוסף תוך כדי איטרציה!

כאשר מוחקים איברים מאוסף מתוך לולאת do: הרצה על אותו אוסף עצמו:
range do: [ :n | n isPrime ifFalse: [ range remove: n ] ] האיטרטור מדלג על איברים עוקבים בשל הסטת האינדקסים לאחור בעת המחיקה!
✅ הפתרון התקני: ביצוע האיטרציה על גבי עותק שאינו משתנה:
range copy do: [ :n | n isPrime ifFalse: [ range remove: n ] ] או שימוש ישיר ונקי ב-select: / reject:!

2. סעיף 6.7: פרימיטיבים (Primitives) ומנגנון הנסיגה (Fallback Code)

למה צריך פעולות ברמת המכונה הווירטואלית (VM), איך נרשם <primitive: 1>, ומתי מורץ קוד ה-Smalltalk שמתחתיו?

אף ש-Smalltalk שואפת לבצע הכל באמצעות עצמים והודעות ברמת התוכנה, קיימות פעולות יסוד בסיסיות החייבות להתבצע ישירות על ידי המכונה הווירטואלית (Virtual Machine – VM) בשפת C או בשפת מכונה – משיקולי יעילות וביצועים, או לצורך גישה ישירה לחומרה ולזיכרון. פעולות אלו נקראות פרימיטיבים (Primitives).

⚡ ארבע קבוצות הפעולות המכוסות ע"י פרימיטיבים:

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

🛠️ תחביר ההגדרה ומנגנון הנסיגה (Fallback Code)

+ aNumber
    <primitive: 1>
    ^ super + aNumber

• תחביר: נרשם בתחילת המתודה בתוך סוגריים זוויתיים עם primitive: ומספר מזהה חד-חד-ערכי (כגון <primitive: 1> לחיבור ב-SmallInteger).
• מנגנון הנסיגה והכישלון (Fallback): אם הפרימיטיב מצליח, הערך מוחזר מיד והמתודה מסתיימת. אם הפרימיטיב נכשל (למשל: ניסיון לחבר מספר שלם עם שבר עשרוני Float, או גלישת זיכרון Overflow שהופכת את המספר ל-LargePositiveInteger), המכונה הווירטואלית מדלגת על הפרימיטיב וממשיכה להריץ את קוד ה-Smalltalk הרגיל הכתוב מתחתיו כתוכנית גיבוי (^ super + aNumber)!

עזר גרפי 6.5 • איטרטורים על אוספים ומנגנון ה-Primitive Fallback

השוואת איטרטורים ומסלול הביצוע של <primitive: 1> במכונה הווירטואלית

Iterators & Primitives
מפת האיטרטורים הפונקציונליים על אוספים • collect: (Map) ⟵ אוסף חדש מתוצאות הבלוק (בעזרת species) • select: / reject: ⟵ אוסף חדש מסונן (true ב-select, ו-false ב-reject) • detect: / detect:ifNone: ⟵ האיבר הראשון שעומד בתנאי (או חריגת NotFound) • inject:into: (Reduce) ⟵ צבירה עם מצבר (acc) והאיבר התורן (each) ⚠️ מחיקה בלולאה: חובה להריץ על עותק (range copy do:)! מנגנון הפרימיטיב (<primitive: 1>) וה-Fallback VM: <primitive: 1> ✅ הצלחה ב-VM! חיבור שני SmallInteger מחזיר מיד (מדלג על הגיבוי) ⚠️ כישלון פרימיטיב! חיבור עם Float או גלישה ➔ ^ super + aNumber
⚡

מיני-סימולציה 6.5: Sandbox חי של פרימיטיבים (<primitive: 1>) וקוד Fallback

בחר תרחיש חיבור ב-SmallInteger וצפה האם הפרימיטיב מצליח או קופץ לקוד הגיבוי
🧪
סימולציה אינטראקטיבית גדולה 6.5

צינור האיטרטורים הוויזואלי (Collection Pipeline Visualizer) ומלכודת המחיקה

לפניך האוסף (1 to: 10) כעשרה עיגולים חכמים. הפעל עליו איטרטור ובחן כיצד האוסף מסונן, מותמר, מאותר או נצבר בזמן אמת (או מה קורה כשמוחקים איברים במהלך do: ללא copy!):

⚡

הגשר הנרטיבי: השלמנו את כל פרק 6 – בוא נסכם את כל מושגי התחביר בכרטיסיות תלת-ממד!

עברנו יחד דרך כל סעיפי התחביר של שפת Smalltalk: מביטויים, ליטרלים ופסאודו-משתנים (6.1–6.2), דרך שליחת הודעות וסדר קדימויות (6.3), מתודות ובלוקים (6.4–6.5), תנאים פולימורפיים, לולאות ואיטרטורים על אוספים (6.6), ועד לפרימיטיבים של המכונה הווירטואלית (6.7). בלשונית המסכמת הבאה נרכז את כל התמונה ונתרגל 15 כרטיסיות זיכרון תלת-ממדיות (3D Flashcards)!

פרק 6: תחביר שפת Smalltalk • יחידה 6.8 אינטגרציה ואינדקס מושגים

מילון מושגים אינטראקטיבי וסיכום מלא של פרק 6

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

🏆
🧠

מודל מנטלי ועיקרון מנחה: כל התחביר של Smalltalk נשען על עצמים, הודעות ובלוקים!

💡 עמוד תווך 1: ביטויים והודעות

6 פסאודו-משתנים (true, false, nil, self, super, thisContext), מערכים סטטיים #() מול דינמיים {}, וסדר קדימויות מוחלט משמאל לימין: סוגריים ← אונרי ← בינרי ← מילות-מפתח.

⚖️ עמוד תווך 2: מתודות ובלוקים

מתודה ללא ^ מחזירה את self; בלוק ללא ^ מחזיר את הביטוי האחרון שלו; ו-^ בתוך בלוק יוצא מיד מהמתודה העוטפת כולה (Non-local return)!

🎯 עמוד תווך 3: בקרה, איטרטורים ופרימיטיבים

תנאים פולימורפיים ב-True/False, לולאות הבנויות על whileTrue:, איטרטורים פונקציונליים (collect:, select:, reject:, detect:, inject:into:) ופרימיטיבים (<primitive: 1>) עם קוד Fallback.

עזר גרפי 6.6 • מפת העל של פרק 6 (סעיפים 6.1–6.7)

איך כל מרכיבי התחביר של Smalltalk משתלבים למערכת הרמונית אחת?

Chapter 6 Master Map
6.1–6.2 ביטויים • 6 פסאודו-משתנים • ליטרלים: $a, #Sym, 2r101 • #() סטטי מול {} דינמי • Cascade (;) לאותו מקבל 6.3 שליחת הודעות • אונרית (3 factorial) • בינרית (3 + 4 * 5 = 35) • מילות-מפתח (at:put:) • קדימות: () ➔ אונרי ➔ בינרי 6.4–6.5 מתודות ובלוקים • 4 חלקי המתודה (^ או self) • בלוקים [...] עם value: • Closure ומשתנים | n | • ^ בבלוק יוצא מהמתודה! 6.6 תנאים, לולאות ואוספים • ifTrue:ifFalse: פולימורפי • and:/or: עצלים מול &/| • whileTrue:, timesRepeat: • collect:, select:, inject:into: 6.7 פרימיטיבים • ביצוע ישיר ב-VM • <primitive: 1> בראש מתודה • זיכרון, בתים, שלמים, מערך • Fallback Code בכישלון!
⚡

מיני-סימולציה 6.6: בוחן מלכודות קוד קלאסיות מפרק 6

לחץ על כל מלכודת כדי לבדוק את האבחנה המדויקת
🃏
סימולציה אינטראקטיבית גדולה 6.6

כרטיסיות זיכרון תלת-ממדיות (3D Flashcards) לכל 15 מושגי פרק 6

לחץ על כל כרטיסייה כדי להפוך אותה ולבחון את עצמך על כל אחד מ-15 מושגי המפתח של פרק 6:

נחשפו: 0 / 15
🚀

סיימנו במלואו את פרק 6 (תחביר שפת Smalltalk)! מה מחכה לנו בפרק 7?

כעת, אחרי ששלטנו בכל רכיבי התחביר של Smalltalk — מביטויים, ליטרלים ופסאודו-משתנים, דרך שלוש תבניות ההודעות וסדר הקדימויות, מתודות ובלוקים, ועד לתנאים פולימורפיים, לולאות, איטרטורים ופרימיטיבים — אנחנו מוכנים להעמיק עוד יותר במנגנון התקשורת של השפה!

🎯 בפרק 7 (שליחת הודעות) נעמיק יחד ב: • זיהוי מדויק של סוגי הודעות ובניית ביטויים מורכבים.
• מוסכמות הסוגריים, סדרות ביטויים ושימושים מתקדמים ב-Cascade!
🕹️

מעבדת חקר אינטראקטיבית במסך מלא

❓

5 שאלות פתוחות מדורגות + 5 שאלות אמריקאיות עם הסברים מלאים לכל יחידה

🚀 השלב הבא שלנו: פרק 7 – שליחת הודעות

סיימנו במלואו את פרק 6: תחביר שפת Smalltalk (סעיפים 6.1 עד 6.7)! בפרק 7 נעמיק יחד בשליחת הודעות: