פיתון למתכנת רובי

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

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

אז מה לא אינטואיטיבי עבורי?  הכל 🙂
סתם, הנה הדגמה קטנה לרובי:

[1, 2, 3, 4].each_with_index do |elem, idx|
puts "arr[#{idx}]=#{elem}"
end

המקביל (למעט שימוש ב format string) של זה בפיתון:

for idx, elem in enumerate([1, 2, 3, 4]):
print('arr[{}]={}'.format(idx, elem))

לפני שאסביר מה מפריע לי, בואו נראה איך זה יתבצע ב ES6 למשל:

[1, 2, 3, 4].forEach( (elem, idx) => console.log('arr[%d]=%d', idx, elem) )

בגו (go/golang) זה יראה ככה:

slice := []int{1, 2, 3, 4}
for idx, elem := range slice {
 fmt.Printf("arr[%d]=%d\n", idx, elem)
}

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

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

בגו, יש לולאה מסוג אחד – for.
הלולאה הזו מסוגלת לבצע איטרציה באמצעות range, או להיות לולאת for כפי שהיא מוכרת בתחביר C.
אין עוד סוגים של לולאות.

אז מה מפריע לי בפיתון?
אני יכול לבצע איטרציה פשוטה של

for elem in [1, 2, 3, 4]:
...

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

אבל בפיתון האיטרציה מסוגלת להתבצע באמצעות הגדרה של שני מתודות: __iter__ ו __next__ (או next בגרסה 2), אז למה אין איטרציה לאינדקס? למה צריך משהו חיצוני בשביל זה?

סבבה, אפשר "לבלוע" את זה, אם זה היה הדבר היחיד.

הנה בעיה נוספת:

ברובי, יש לי פעולה כזו:

[1, 2, 3, 4].join(',')

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

לרשימה בפיתון אי אפשר לעשות אותו הדבר. צריך לעשות את זה בגישה לגמרי שונה:

','.join(str(n) for n in [1, 2, 3, 4])

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

','.join([1, 2, 3, 4])

הוא יצעק עלי:

TypeError: sequence item 0: expected str instance, int found

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

אפילו ES עושה את זה בגישה שאני מצפה לה:

[1, 2, 3, 4, 5].join(',')

כלומר זה לא ייחודי לרובי.

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

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

רובי:

[1, 2, 3, 4].length
[1, 2, 3, 4].count

ES:

[1, 2, 3, 4].length

פיתון:

[1, 2, 3, 4].count() # TypeError: count() takes exactly one argument (0 given)
# oh, it looks up if an element exists... and how many times, okay then ...

len([1, 2, 3, 4])

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

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

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

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

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

אה, ועוד דבר, ברובי, count לא קיים למחרוזת אמנם, אבל בכל מה שיורש מ enumerable (כדוגמת מערך ו hash) ב3 צורות:

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

עבור Hash שימוש מספר 2, תמיד יחזיר 0, כי הבדיקה מורכבת מעט יותר, הרי על מה אנחנו רוצים לבצע את הספירה? על מפתחות? סבבה, יש מתודה בשם keys. על ערכים? סבבה, יש מתודה בשם values. שתיהן מחזירות מערכים. לבדוק אם מפתח וערך מתקיימים ביחד, זה לא הגיוני עבור count להחזיר, כי לא יכולים להיות 2 מפתחות באותו השם. לבדוק האם מפתח קיים, משתמשים ב include?‎ (סימן השאלה בסוף). אותו הדבר לגבי מערך, אותה מתודה תחזיר האם איבר קיים, שוב פעם ירושה, הפעם מ BasicObject.

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

2 מחשבות על “פיתון למתכנת רובי

  1. phpandmore

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

    1. ik_5 מאת

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

      אבל הבעיה עדיין נשארת אין אחידות בשפה.

להשאיר תגובה

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

הלוגו של WordPress.com

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

תמונת Twitter

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

תמונת Facebook

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

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

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

מתחבר ל-%s