זרימת בקרה

SUB#

מחזיר הפניה לתת־השגרה המתבצעת כעת.

__SUB__ הוא טוקן ההפניה־העצמית: בתוך sub, הוא מוערך להפניית קוד המצביעה על אותו sub עצמו, מבלי שה־sub יצטרך לדעת את שמו. מחוץ לכל sub, הוא undef. הסיבה הנפוצה לפנות אליו היא רקורסיה אנונימית - sub המאוחסן בלקסיקלי או המועבר כ־callback זקוק לדרך לקרוא לעצמו שאינה תלויה בשם מוסמך־חבילה, ו־__SUB__ הוא הדרך הזו.

תקציר#

__SUB__
__SUB__->(@args)
goto __SUB__

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

מה מקבלים בחזרה#

הפניית קוד (אותו דבר ש־\&name היה נותן עבור sub בעל שם), או undef כאשר מוערך מחוץ לכל תת־שגרה. ההפניה היא ל־sub של זמן ריצה, כולל כל closure מעל לקסיקליים שנלכדו בזמן יצירת ה־sub, כך שקריאה אליו רקורסיבית רואה את אותה סביבה לכודה.

my $fact = sub {
    my $n = shift;
    $n < 2 ? 1 : $n * __SUB__->($n - 1);
};

print $fact->(5);               # 120

צורת הקריאה היא הפעלת coderef רגילה: __SUB__->(@args) או &{__SUB__}(@args). שום ארגומנט אינו מועבר במשתמע; הרקורסיה חייבת להעביר אותם הלאה.

מדוע לא פשוט להשתמש בשם של ה־sub#

subs בעלי שם כבר יכולים לבצע רקורסיה לפי שם - factorial(...) בתוך sub factorial עובד. __SUB__ מוצדק בשלושה מצבים שבהם שם אינו זמין, אינו אמין, או יקר:

  • subs אנונימיים. ל־sub אנונימי המוקצה ללקסיקלי אין שם מוסמך־חבילה יציב. מעקף נפוץ לפני 5.16 היה לכידת הפניה ל־\&name או שימוש בתכסיסי Y-combinator; __SUB__ מחליף את שניהם.

  • subs המועברים כ־callbacks. sub שנמסר ל־API אחר (משווה sort, מטפל אירועים, צעד איטרטור) עשוי להיקרא בחזרה לפני שיש לו שם הנראה באתר הרקורסיה.

  • רקורסיית tail call באמצעות goto. goto __SUB__ משתמש מחדש במסגרת הקריאה הנוכחית וקופץ לראש אותו sub עם כל @_ שהכנת, מה שחשוב לרקורסיה עמוקה שאחרת הייתה מפוצצת את המחסנית.

מצב גלובלי שהוא נוגע בו#

__SUB__ קורא את מסגרת הקריאה המתבצעת כעת - אותה מסגרת ש־caller בוחן. הוא אינו קורא או משנה משתנים מיוחדים כלשהם.

דוגמאות#

factorial אנונימי, המקרה מספר הלימוד:

my $fact = sub {
    my $n = shift;
    $n < 2 ? 1 : $n * __SUB__->($n - 1);
};
print $fact->(6);               # 720

סכום tail-רקורסיבי עם goto __SUB__ - ללא צמיחת מחסנית לכל איטרציה, כי goto משתמש מחדש במסגרת הנוכחית:

my $sum = sub {
    my ($acc, @rest) = @_;
    return $acc unless @rest;
    @_ = ($acc + shift @rest, @rest);
    goto __SUB__;
};
print $sum->(0, 1..1_000_000);  # 500000500000

מעבר על מבנה נתונים מקונן ללא מתן שם להולך:

my $count_leaves = sub {
    my $node = shift;
    return 1 unless ref $node eq 'ARRAY';
    my $n = 0;
    $n += __SUB__->($_) for @$node;
    return $n;
};
print $count_leaves->([1, [2, 3], [[4], 5]]);   # 5

מחוץ לכל sub, __SUB__ הוא undef:

print defined(__SUB__) ? "yes" : "no";          # no

נלכד, לא נפתר־בעת־קריאה. __SUB__ מוערך בכל פעם שהוא רץ, במסגרת שבה הוא רץ - כך שב־sub מקונן הוא מתייחס ל־sub הפנימי:

sub outer {
    my $inner = sub { __SUB__ };    # inner's own coderef
    return $inner->();
}
my $ref = outer();
print $ref == $ref;                 # 1 - stable coderef

מקרי קצה#

  • דורש את התכונה current_sub. __SUB__ מותנה ב־use feature 'current_sub', מופעל כברירת מחדל תחת use v5.16 או כל חבילת גרסה מאוחרת יותר. קוד המכוון לרמות תחביר ישנות יותר חייב להצטרף במפורש, או להוסיף קידומת CORE:: לטוקן כ־CORE::__SUB__.

  • אין צורת סוגריים. __SUB__() הוא שגיאת תחביר - הטוקן אינו פונקציה, הוא מילת מפתח המוערכת ל־coderef. קרא לתוצאה באמצעות __SUB__->() או &{__SUB__}().

  • מחוץ לכל sub, כולל בתחום הקובץ ובתוך בלוקי BEGIN, UNITCHECK, CHECK, INIT, ו־END, __SUB__ מחזיר undef. קריאה אליו (__SUB__->()) במיקומים אלה מתה עם Can't use an undefined value as a subroutine reference.

  • בתוך בלוקי eval, __SUB__ מתייחס ל־sub העוטף, לא ל־eval - eval { ... } אינו יוצר מסגרת sub חדשה למטרה זו. בתוך eval EXPR חל אותו כלל: הטוקן נפתר ל־sub החיצוני.

  • בתוך בלוק קוד של regex (/(?{...})/ או /(??{...})/), התנהגות __SUB__ כפופה לשינוי - upstream מתעד זאת כלא־יציב. אל תסתמך על מה שהוא מחזיר שם.

  • זהות בין קריאות. שתי הערכות של __SUB__ בתוך אותו sub מניבות את אותו coderef (== משווה אמת). בין closures שונים שנעשו מאותו sub { ... }, כל closure רואה coderef משלו - אחד לכל סביבה לכודה.

  • goto __SUB__ לעומת __SUB__->(). צורת goto משתמשת מחדש במסגרת הקריאה הנוכחית ומחליפה את @_ בכל מה שהקצית לפני ה־goto; היא אינה מגדילה את מחסנית הקריאות. צורת קריאת coderef יוצרת מסגרת חדשה בכל פעם, כך שהיא פוגעת בגבול עומק הרקורסיה על קלטים רקורסיביים עמוקים.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • sub - מגדיר את תת־השגרה שאת ההפניה אליה __SUB__ מחזיר; המקום הראשון לחפש בו כאשר רקורסיה אנונימית היא הכלי למשימה

  • goto - מצטרף ל־__SUB__ עבור רקורסיית tail call באמצעות goto __SUB__, האידיום שמונע צמיחת מחסנית

  • caller - בחן את אותה מסגרת קריאה מבחוץ (חבילה, קובץ, שורה) במקום לתפוס הפניה אליה

  • ref - סווג את מה ש־__SUB__ החזיר; תמיד "CODE" בתוך sub, מחרוזת ריקה מחוץ

  • return - הדרך הרגילה לצאת מה־sub שאת ההפניה אליו __SUB__ מחזיק; רלוונטי בעת בחירה בין רקורסיה ל־tail call באמצעות goto __SUB__