• בלוג
  • perl
  • השוואת התחביר לפונקציות מקוננות בין השפות perl, python ו ruby

השוואת התחביר לפונקציות מקוננות בין השפות perl, python ו ruby

20/04/2017

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

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

1. פרל: שני סוגים של פונקציות

מתכנתי פרל קיבלו את האפשרות להתיחס לפונקציה כטיפוס נתונים החל מגירסא 5 של השפה (דצמבר 1994), אבל בצורה קצת עקומה. גירסא 5 הוסיפה רעיון שנקרא References, שזה סוג של Pointers רק קצת יותר בטוחים לשימוש. יכולת לקבל Reference לסקאלר, לרשימה, ל Hash וכמובן לפונקציה. בנוסף נכנס תחביר חדש ליצירת Reference לפונקציה אנונימית וזו היתה הדרך המקובלת להגדיר פונקציה בתוך פונקציה.

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

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

use v5.16;

sub outer {
  my $x = 0;
  sub {
    say ++$x;
  }
}

my $f = outer;
$f->();
$f->();
$f->();

המילה sub ב perl מגדירה פונקציה. קריאה ל sub עם שם מגדירה פונקציה רגילה וקריאה ל sub בלי ציון שם לפונקציה מגדירה פונקציה אנונימית ומחזירה Reference אליה.

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

use v5.16;

sub outer {
  sub inner {
    5
  }
  7
}

# print 7
say outer();

# print 5
say inner();

2. רובי: אותו רעיון בתחביר כל כך שונה

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

def outer
  x = 0
  proc do
    x += 1
    puts x
  end
end

f = outer
f.call
f.call
f.call

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

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

3. פייתון: גישה חדשה, בעיות חדשות

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

def outer():
    x = 0
    def inner():
        x += 1
        print(x)

    return inner

הפתרון בפייתון2 קצת מסורבל, אבל בפייתון3 כבר קיבלנו את הפקודה nonlocal שמאפשרת לכתוב את המימוש העובד הבא:

def outer():
    x = 0
    def inner():
        nonlocal x
        x += 1
        print(x)

    return inner

f = outer()
f()
f()
f()

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