ההבדל בין לדעת תחביר לבין לדעת שפה

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

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

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

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

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

עכשיו אחרי שנרגענו קלות מהפיצ'ר המדהים הזה, אולי תתחילו לראות בעיות כדוגמת "איך אני משנה ערכים ועושה עליהם מניפולציה, כדוגמת מחרוזת ? הרי, אני בסה"כ מתעסק עם ערכים קבועים". כאן בעצם מגיע ההבדל בין להכיר את התחביר לבין לדעת את השפה. בשפות כדוגמת C או פסקל, אנחנו נשתמש או ב concat, או ב Format String בהתאם לצורך,  אבל בפיתון ביצוע concat הוא לא דבר יעיל, ממש כמו ש a := a +1  בפסקל הוא לא יעיל, וצריך להשתמש בכלים הנכונים של השפה.

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

...
type
  TRecord = record
     s : string;
     i : integer;
  end;
...
procedure InitRecord(out rec : TRecord);
begin
   FillChar(rec, #0, sizeof(TRecord));
...
end;
...
עכשיו תשאלו מה הבעיה בקוד הבא: ובכן הבעיה היא פשוטה. מחרוזת שהיא לא shortstring (נו עד 255 תווים שאתם מכירים מבית ספר) בפסקל, הוא מצביע, כאשר התחביר מאפשר לנו (כמעט תמיד) להתעלם מכך שמדובר במצביע, ולהתנהג כאילו שזו מחרוזת רגילה לכל דבר ועניין.
יש כאלו שהעיניים שלהם נפקחות קצת כרגע… ובכן בפעם הראשונה שהפרוצדורה InitRecord תקרא כאשר הפרמטר rec  לא יכיל שום דבר (היא תהיה בכל מקרה מאופסת), אז אין בעיה. טוב כמעט ואין בעיה. הבעיה היא ש Fillchar, יגיד לS להצביע על תא בזכרון 0. אבל בגלל שגם ככה המחרוזת אפילו לא אותחלה, אין לנו בעיה עם זה. אבל בפעם השנייה שנקרא לפרוצדורה, אנחנו נגרום לדליפת זכרון, כי אנחנו נגיד שוב לאיבר להצביע על כתובת 0, בלי לשחרר את הכתובת הקודמת. שימו לב שעל integer זה לא משפיע.. אבל אותה בעיה תכול על כל משתנה שהוא מצביע (כדוגמת class).
אז עכשיו מגיע העניין החדש שלמדתי: ישנה פקודה נחמדה בשם finalize. הפרוצדורה בעצם תשחרר את כל האיברים הדינמיים ברשומה, ואז אנחנו נוכל להשתמש כמו שצריך ב InitRecord.
אם אתם לא אוהבים את הפרוצדורה, אתם כמובן מוזמנים לכתוב אחת משל עצמכם (רק אל תשכחו שצריך את typeinfo בשביל זה).

9 מחשבות על “ההבדל בין לדעת תחביר לבין לדעת שפה

  1. Meir Kriheli

    אני מסמיק, איזו בושה.

    תיקון קטן: ה-reference הזה הוא רק ל-immutables, טיפוסים שלא יכולים להשתנות. לטיפוסים שיכולים להשתנות ה-references נפרדים. דוגמא:


    >>> a = 'yo'
    >>> b = 'yo'
    >>> a == b
    True
    >>> a is b
    True
    >>> a = []
    >>> b = []
    >>> a == b
    True
    >>> a is b
    False

    בנוסף כדאי לזכור שטיפוסי immutable מועברים לפונקציות לפי ערך, בעוד mutables מועברים לפי reference.

  2. שי

    מאיר כתב:
    """
    בנוסף כדאי לזכור שטיפוסי immutable מועברים לפונקציות לפי ערך, בעוד mutables מועברים לפי reference
    """
    אני לא יודע איך השפה ממומשת, אבל מבחינת המשתמש, אין שום הבדל אם אובייקט immutable מועבר לפי ערך או reference. צריך גם לשים לב שהמשמעות של "מועבר by ref" בפייתון לא זהה למשמעות בשפות כמו C או פסקל:


    >>> def f(a,b):
    ... a.append(17)
    ... b = 17
    ...
    >>> x = [1,2]
    >>> y = 34
    >>> z = [3,4]
    >>> f(x,y)
    >>> x
    [1, 2, 17]
    >>> y
    34
    >>> f(x,z)
    >>> x
    [1, 2, 17, 17]
    >>> z
    [3, 4]
    >>>

  3. ארתיום

    מספר הערות:

    אחד הדברים החשובים ביותר בשפה זה להבין מה קורה מתחת לפני השטח.

    פעולה FillChar (כפי שאני אני מבין היא המקבילה של memset של C) היא נועדה לפעולות ברמה נמוכה יותר. זה כמו שאני אכתוב:

    std::string x; memset(&x,0,sizeof(x)); //end

    יתלו אותי על עץ גבוהה ובצדק. כי אני אהרוג מצביע או שניים. מצד שני, אם אתה כן יודע מה קורה אתה יכול גם לכתוב את הקוד כמו שצריך.

    למשל, משהו שלא ידעתי עד לא מזמן, שב-++C יש סמנטיקה של std::string דומה למחרוזות ב-Pascal – קרי refcounter+copy on write וזה תמיד מועדף להעביר אותם לפי ערך.

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

    ד"א אתה יכול להסביר לי מה לא יעיל ב:
    1+ a:=a
    ?

  4. ik_5 מאת

    "זה כמו שאני אכתוב:
    std::string x; memset(&x,0,sizeof(x)); //end
    יתלו אותי על עץ גבוהה ובצדק."

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

    אצלי אין את הבעיה הזו. אני יודע מה הכיוון שאני הולך.

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

    לגבי a := a +1, ב TP הוצגו 2 פרוצדורות inc ו dec, והאופטימיזציה עבדה טוב עליהם, אבל לא על הביטוי של a:= a + something, והשימוש נשאר. בגלל זה הרבה תוכנות של מפתחי פסקל ישתמשו לך בזה במקום הביטוי המורגל.

    ד"א FPC הציג גם את התחביר שך += וחבריו…

  5. ארתיום

    פשוט
    a := a +1
    תמיד, אבל תמיד יתורגם ע"י כל קומפיילר סביר לפעולת Inc או dec של המכונה המתאימים. זאת אחת השגיאות הנפוצות ביותר של מתכנתים (במיוחד מתכנתי C) שמכנסים לדחוף לאותה שורה/ביטוי כמה שיותר לוגיקה בהנחה שזה יעבוד מהר יותר. אני מוכן להתערב איתך שאם תקמפל את הקוד עם אופטימיזציה בסיסית ב-pascal עם inc ועם a:=a+1 אתה תקבל בדיוק אותה תוצאה ב-assembly.

    אגב, בדיקה מהירה שעשיתי אכן הראתה שצדקתי🙂

  6. ארתיום

    נכון, אבל הפעם הזה היה מזמן, ורבים שוכחים את זה.

    למשל פעם עבדתי תבחום Embedded ב-C וכמובן לא היה שם floating point כך שנדרשנו לעובד עם מספרים שלמים כל הזמן. אז לעיתים הייתי רואה אנשים כותבים

    x=(x+2)>>2
    במקום
    x=(x+2)/4
    שזה בד"כ הרבה יותר קריא וברור וגם תמיד מתורגם בפועל לאותו קוד Assembly.

    לפעמים זה מאוד מעניין לראות מה gcc מסוגל לעשות לקוד המקומפל עם O3- — אפילו אני לא הייתי עולה על זה🙂

  7. z4ziggy

    ארתיום:
    תלוי למה אתה עושה את ה SHIFT. אם אתה באמת רק רוצה לחלק ב 4, אז ברור שעדיף להשתמש בחילוק. אבל אם אני עושה את ה SHIFT כדי לעבוד עם 6 הביטים הנותרים אז אני מעדיף לראות את ה SHIFT בתוכנית מאשר סימן חילוק שאז אני אתחיל לשאול את עצמי "למה לכל הרוחות הוא מחלק ב 4???". אז כמו שאמרתי – זה תלוי.

    ik_5:
    finalize היה שם תמיד, בטח שבדלפי ואני חושב שאפילו בטורבו פסקל – אני לא בטוח. ולגבי האיפוס של ה STRING – כידוע בפסקל ביט 0 הוא האורך של ה STRING וניתן פשוט לאפס אותו כדי להורות על אורך 0.
    ולגבי האיפוס של ה string עם FillChar, אני באמת שלא מבין מה הבעיה. FPC יודע שאתה מתכוון לתוכן של ה string ולא למצביע שלו ומאפס את ה string ולא את המצביע. עשיתי עכשיו ניסוי עם עזריאל ו gdb וכפי שצפיתי אין שום בעיה (זליגת זיכרון, איפוס מצביע, או כל בעיה אחרת) עם ה FillChar. בבקשה, אני מאוד אודה לך להצביע על משהו שאני מפספס כדי שגם אוכל ללמוד🙂
    לעומת זאת, הבעיה שכן מצאתי עם ה FillChar היא שהערך ב i לא התאפס…

  8. ik_5 מאת

    z4ziggy, אכן finalize קיים הרבה שנים, רק אני לא הכרתי אותו (
    בכל מקרה, במחרוזת פסקל, רק shortstring עובד על תא 0. הטיפוס של AnsiString (שבד"כ יאופשר אם תעבוד עם $H+ אז הוא יאופשר בברירת מחדל.

    הקדשתי עכשיו שעה של ניסויים שונים, וגם אני לא הצלחתי לשחזר את זה. מה שאומר שכנראה שFPC חסין בביצוע שלו (בניגוד לדלפי, לפי הכותב המקורי): http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html

    מוזר…

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s