במהלך הבנייה בזמן-אמת של תוכנית הלימודים לשיעור "מבוא למדעי המחשב", מצאתי את עצמי מתכנן ומממש שפת תכנות סופר-פרימיטיבית חדשה. בפוסט זה אסביר למה יצרתי אותה ואיך היא עובדת.
אף על פי שאני לא ממש מת על פייתון, ברור לי שכאשר יגיע השלב המתאים – אני מלמד מדעי המחשב בהתנדבות בבית הספר של הילדים שלי – זו תהיה שפת התכנות שאבחר ללמד. היא מאפשרת להגיע לתוצאות מספיק מעניינות בלי לייאש 90% מהתלמידים בדרך, קל מאוד לארגן סביבת פיתוח נוחה עבורה (יתרון חשוב כשהילדים לומדים מהבית) והיא גם רלוונטית לעולם התוכנה המודרני שבחוץ.
יחד עם זאת, היה לי חבל לוותר על תובנות low-level מסוימות, שקשה להגיע אליהן דרך פייתון אבל נראות לי חיוניות כדי להבין ולחוות מחשבים ואלגוריתמים. ללמד שפה נוספת, כמו C או אסמבלי, רק בשביל לסגור את הפינה הזו יהיה כמובן טירוף מוחלט ובזבוז זמן משווע. להסביר את התובנות האלה בעל-פה זה כמו ללמד שחייה בהתכתבות. מה עושים?
וכך, באמצע מקלחת בשתיים בלילה (מכאן השם "מיק-2"), חשבתי פתאום להמציא מעין שפת תכנות מינימליסטית דמוית אסמבלי, מוגבלת ופשוטה במידה קיצונית, שאפשר ללמד במלואה תוך שיעור אחד או פחות, ושכל מטרתה בחיים לתרגל ולהתרגל לאופן שבו מחשבים "חושבים" ועובדים. אחרי מספר איטרציות בראש, החלטתי גם לכתוב מפענח (interpreter) לשפה הזו – הוא ייכתב בפייתון כמובן – כדי שאפשר יהיה להריץ תוכניות ממש ולא רק על הנייר. השפה לא נועדה להיות שימושית לשום דבר חוץ מהתחלת למידה, היא לא נועדה להיראות טוב, ולמי שמבין – אני גם לא בטוח שהיא Turing-complete.
המפרט של מיק-2
כל תוכנית מבוססת על מערך של מספרים שלמים בזיכרון, על משתנה מובנה אחד ויחיד – רגיסטר, ליתר דיוק – ועל סט של ארבע פקודות בלבד. הפקודות הן:
- RD – העתקה של ערך מאינדקס בזיכרון אל הרגיסטר
- WR – העתקה של הערך מהרגיסטר אל אינדקס בזיכרון
- SU – הפחתה של ערך מאינדקס בזיכרון מהערך שברגיסטר
- JZ – קפיצה לפקודה אחרת אם הערך שברגיסטר הוא אפס
כל תוכנית מתחילה בשורה אחת של אתחול המערך בזיכרון (אנחנו כותבים בה את האינדקסים והערכים שמעניינים אותנו), ואחריה שורות הפקודות. השורות אינן ממוספרות במפורש – המספור מתבסס על סדר הופעתן בקוד, כשהפקודה הראשונה נחשבת פקודה אפס. אפשר להוסיף הערות באמצעות התו "#" (המפענח יתעלם ממנו ומכל מה שאחריו, עד סוף אותה שורה). מספרים נכתבים בייצוג עשרוני רגיל, אינדקסים נכתבים כמספרים מוקפים בסוגריים מרובעים.
מותר לכתוב (WR) לאינדקס במערך שלא אותחל בשורה הראשונה. אם קוראים (RD) מאינדקס במערך שלא צוין קודם לכן, הערך שיוחזר יהיה 0. אם מגיעים או קופצים לפקודה מעבר למספר הפקודות שקיימות בקוד, התוכנית מסתיימת. ואם כותבים ערך לאינדקס [-1] במערך, המפענח ישלח למסך את התו שתואם לערך הזה.
הנה תוכנית לדוגמה:
[0]=1234, [5]=33 RD [0] WR [1] RD [5] WR [-1]
השורה הראשונה מגדירה שבאינדקס [0] במערך המספרים יהיה הערך 1234, ובאינדקס [5] יהיה הערך 33. הפקודה הראשונה (RD) קוראת לרגיסטר את הערך מ-[0], הפקודה הבאה מעתיקה את הערך מהרגיסטר לאינדקס [1] במערך, וזוג הפקודות הבאות בעצם מציג כפלט את התו 33 – סימן קריאה ב-ASCII – כדי לומר לנו, נניח, שהפעולה התבצעה. כלומר, התוכנית כולה בעצם מעתיקה ערך ממקום אחד בזיכרון למקום אחר, ומציגה משוב בסיסי מאוד.
דוגמאות מתקדמות קצת יותר
נניח שאנחנו רוצים תוכנית שתכתוב לאינדקס [1] מספר שגדול ב-1 מהערך שבאינדקס [0]. הרעיון החינוכי כאן הוא שהתלמידים יגידו, קודם כל, "בלתי אפשרי – הרי אין פקודת חיבור, רק פקודת חיסור" – אבל אחרי קצת מחשבה הם יעלו על התובנה המתמטית הבסיסית, שחיבור זה כמו חיסור של מספר שלילי. ואז:
[0]=1234,[2]=-1 RD [0] SU [2] WR [1]
ועל בסיס אותה תובנה, רק עם מימוש טיפה יותר מורכב, תוכנית שמחברת שני ערכים (נניח מאינדקסים [0] ו-[1]) ושמה את התוצאה באינדקס [2]:
[0]=123,[1]=456 RD [999] SU [0] WR [999] RD [1] SU [999] WR [2]
בשלב הבא, הם יצטרכו לכתוב תוכנית שבודקת אם הערכים בשני אינדקסים במערך הם זהים או שונים. כאן, כיוון שיש שתי אפשרויות לתשובה, נצטרך להיעזר בפקודת הקפיצה המותנית JZ – ולגמרי במקרה, התנאי שהיא בודקת מסתדר לנו יפה עם המתמטיקה של המשימה:
[0]=123,[1]=124,[20]=83,[21]=68 RD [0] # This is line 0 SU [1] JZ 5 RD [21] WR [20] RD [20] # This is line 5 WR [-1]
יש כמה דרכים לבצע את המשימה הזו, וכמובן צריך להריץ את התוכנית לפחות פעמיים, פעם כשהמספרים ב-[0] וב-[1] זהים ופעם כשהם שונים, כדי לראות שהיא עובדת נכון בשני המצבים. במימוש לעיל, הגדרתי כברירת מחדל את התשובה 83 (התו "S", מייצג Same) בכתובת [20]. מחסירים מספר אחד מהשני. אם התוצאה היא 0 (כלומר הם שווים), התוכנה קופצת לשורה 5 – קריאה של הערך ב-[20] וכתיבתו למסך. אם התוצאה שונה מ-0, המפענח ימשיך לפקודה שמייד אחרי JZ – העתקה של התשובה האחרת, 68 (D ל-Different) לכתובת [20], ואז זה מה שיוצג על המסך.
בוודאי שמתם לב שהפרמטר לפקודת JZ לא מוקף בסוגריים מרובעים. זה כי מדובר בערך בפני עצמו, לא באינדקס. אינדקס לא יתקבל בפקודה הזו, וערכים ללא סוגריים מרובעים לא יתקבלו בפקודות האחרות. נכון, אפשר היה להוסיף את האופציות האלה לשפה בלי קושי, אבל שוב – הרעיון הוא פשטות מצד אחד, ולהכיר קצת סטנדרטים של כתיבת קוד מצד שני.
ועכשיו אתם: מה עושה התוכנית הזו?
[0]=57,[1]=47,[2]=1 RD [0] WR [-1] SU [2] WR [0] SU [1] JZ 999 RD [10] JZ 0
ומה הלאה
השפה הקטנה והמאולתרת הזו לא אמורה להיות בשימוש זמן רב. אם הילדים ישתפו פעולה, הם יכולים להפיק את כל מה שאני מקווה שיפיקו ממנה תוך שני שיעורים, בערך. עם זאת, בשביל התירגול שלי-עצמי וכדי לוודא שאני לא מפדח את עצמי עם קוד שגוי בשפה שאני המצאתי, כתבתי את המפענח בפייתון. כדי להשתמש בו – ואני לא אחראי לתוצאות – צריך לכתוב קובץ קוד מיק-2 בנפרד, ולכתוב את שם קובץ הקוד הזה במשתנה PROGRAM_FILE בתחילת קוד המפענח.
טבילת האש של השפה החדשה תתרחש לקראת סוף השבוע. אם יהיו לי תובנות חדשות מההתנסות בשטח, אכתוב עליהן בפוסט נפרד. יש לכם רעיונות לתוכניות קצרות ומעניינות נוספות?
אז אתה מגדל את הדור החדש של מתכנתי מכונות טיורינג?
(או BF)
ירצו יאכלו, לא ירצו לא יאכלו 🙂
השפה עברה אצל הילדים בשלום?
לדעתי הוספת אנימציה של פעולת ה"מעבד" על מרחב הזכרון שלך בהחלט יכולה להוסיף לילדים עניין והבנה (אבל זה מסבך לך את העבודה מאוד).
בנוסף, אני מבין ש-999 היא הכתובת האחרונה במרחב הזיכרון?
האמת, אני לא יודע עדיין אם זה עבד כמו שרציתי. לא נרשמה נטישה המונית או משהו, היו שאלות ענייניות, אבל בסופו של דבר הספקתי רק להציג את השפה + דוגמה קטנה, וברוח ימים אלה אף אחד לא טרח לענות בינתיים על שיעורי הבית. בשיעור הבא נראה מה הם זוכרים ואיך הם יתמודדו עם "קוד" טיפה יותר מורכב, ואז בשאיפה גם אני אהיה יותר חכם. אנימציה אכן לא ריאלית מבחינתי. אני כן יכול להדפיס את מפת הזיכרון העדכנית לצד כל פעולה שרצה, אבל בשלב כל כך מוקדם הם יתקשו להריץ את האינטרפרטר אצלם, ומהצד שלי יותר נוח פשוט לצייר על שיתוף… לקרוא עוד »
בהצלחה מניסיון שלי קשה מאוד לעניין ילדים במשהו כזה אולי שהקומפילר ידפיס משהו בהתאם למה שהם מתכנתים אולי מכונת מצבים פשוטה
לא קומפיילר, אינטרפרטר 🙂 ללא ספק, יש פתרונות הרבה יותר יפים ממה שעשיתי כאן, אבל אני כן רוצה שהם יחוו low-level בראש, את צורת המחשבה עצמה, שאפשר לקחת אחר כך לכל סביבה ושפה אחרת. וזה יהיה כאמור חלק קטן מאוד מהלמידה הכללית, ובהנחייה מאוד מסודרת. ויכול להיות כמובן שהם באמת ישתעממו ויאבדו אותי, נראה… באילו מסגרות לימדת?
תעדכן איך היה, העברתי כמה קורסי ארדואינו כיתות ז-ט