זרימת בקרה

goto#

להעביר את הביצוע למקום אחר בתוכנית מבלי לחזור.

ל־goto יש שלוש צורות לא־קשורות שחולקות רק מילת מפתח. השימושית ב־Perl מודרני היא goto &NAME - צורת הtail-call, שמחליפה את המסגרת הנוכחית של תת־השגרה בקריאה לתת־שגרה אחרת תוך שמירה על @_. צורות goto LABEL ו־goto EXPR קיימות, מוגבלות, וכמעט אף פעם אינן הכלי הנכון.

תקציר#

goto LABEL
goto EXPR
goto &NAME

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

goto אינו חוזר. הבקרה עוזבת את הוראת ה־goto ואינה חוזרת. ערך ההחזרה של המבנה שאליו ה־goto נוחת לבסוף הוא הערך של הביטוי העוטף.

עבור goto &NAME, ערך ההחזרה הוא מה שתת־השגרה היעד מחזירה, מופץ לקורא המקורי כאילו היעד נקרא ישירות.

צורת ה־tail-call: goto &NAME#

זוהי הצורה שחשובה. היא יוצאת מתת־השגרה הנוכחית - מבטלת כל קישורי local - ומיד קוראת ל־NAME עם ה־@_ הנוכחי. המסגרת הנוכחית מוחלפת, לא מוערמת מעליה.

השלכות:

  • caller בתוך תת־השגרה היעד רואה את הקורא המקורי, לא את תת־השגרה שביצעה את ה־goto. מסגרת הביניים נעלמה.

  • @_ מועבר כפי שהוא. שינויים ל־@_ לפני ה־goto נראים ליעד.

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

  • היעד אינו חייב לדעת שהגיעו אליו דרך goto &; מנקודת מבטו הוא נקרא רגיל.

השימוש הקנוני הוא דיספאץ« של AUTOLOAD: לפענח את תת־השגרה האמיתית, ואז goto אליה כך ש־stack traces, caller, ו־wantarray כולם מתנהגים כאילו תת־השגרה האמיתית נקראה מלכתחילה.

NAME אינו חייב להיות שם מילולי. הוא יכול להיות ביטוי כלשהו שמניב הפניית קוד - סקלר שמחזיק coderef, בלוק שמחזיר אחד, או חריץ שעבר dereference.

goto &$coderef;
goto &{ $dispatch{$op} };

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

צורת goto LABEL#

מוצא את ההוראה עם התווית LABEL בתוך התחום הדינמי הנוכחי וממשיך ביצוע שם. בפועל זה אומר שהוא יכול לקפוץ החוצה מבלוקים וחוץ מתת־שגרות, אך הוא אינו יכול לקפוץ אל תוך מבנה שדורש אתחול - גוף תת־שגרה, לולאת foreach, בלוק given, פרמטר של אופרטור בינארי או רשימה, או קוד שהאופטימייזר הסיר. קפיצה אל תוך מבנה כזה היא שגיאה קטלנית החל מ־Perl 5.44.

את רוב מה ש־goto LABEL יכול לעשות עדיף לבטא עם בקרת לולאה עם תוויות (last LABEL, next LABEL, redo LABEL) או עם חריגות (die / eval). יש לפנות אליהם קודם.

צורת goto EXPR#

מעריך את EXPR. אם הוא מניב הפניית קוד, מתנהג כמו goto &NAME. אם הוא מניב מחרוזת, מחרוזת זו משמשת כשם תווית שמפוענחת בזמן ריצה - goto מחושב. אותן הגבלות כמו של goto LABEL חלות על מקרה התווית.

שתי מוזרויות parser לזכור:

  • goto EXPR פטור מהכלל ״נראה כמו פונקציה״. סוגריים אחרי goto אינם בהכרח מתחמים את הארגומנט שלו: goto("NE")."XT" הוא goto NEXT, לא goto "NE" שאחריו שרשור חסר תועלת.

  • ל־goto יש אותה קדימות כמו השמה, שלא כמו רוב האופרטורים נקובי־השם.

דוגמאות#

דיספאץ« tail-call מ־AUTOLOAD - תת־השגרה היעד רואה את הקורא המקורי, לא את AUTOLOAD:

sub AUTOLOAD {
    our $AUTOLOAD;
    my ($method) = $AUTOLOAD =~ /::(\w+)\z/;
    my $code = $self->can_really($method)
        or die "no such method: $method";
    goto &$code;              # @_ forwarded, caller() unchanged
}

רקורסיית tail ללא הגדלת מחסנית הקריאה. __SUB__ מפוענח לתת־השגרה שרצה כעת:

sub fact {
    my ($n, $acc) = @_;
    $acc //= 1;
    return $acc if $n <= 1;
    @_ = ($n - 1, $acc * $n);
    goto __SUB__;
}

goto מחושב דרך goto EXPR. התווית נבחרת בזמן ריצה מרשימה:

goto ("FOO", "BAR", "GLARCH")[$i];
FOO:    handle_foo();    return;
BAR:    handle_bar();    return;
GLARCH: handle_glarch(); return;

האצלה ל־coderef שמוחזק בטבלת דיספאץ«. הקורא לעולם לא רואה את מסגרת הביניים:

my %ops = (
    add => sub { $_[0] + $_[1] },
    mul => sub { $_[0] * $_[1] },
);

sub run_op {
    my $op = shift;
    goto &{ $ops{$op} // die "unknown op: $op" };
}

שינוי @_ לפני tail call - שינויים נראים ליעד:

sub wrap {
    unshift @_, "prefix";
    goto &real_handler;       # real_handler sees ("prefix", @orig_args)
}

מקרי קצה#

  • goto LABEL אינו יכול להיכנס לתת־שגרה, foreach, או given. החל מ־Perl 5.44 זוהי שגיאה קטלנית, לא אזהרה. הוא גם אינו יכול לקפוץ לתוך קוד שעבר אופטימיזציה החוצה או לרוב אופרנדים של אופרטורים - היוצא מן הכלל היחיד הוא האופרנד הראשון של אופרטור בינארי (עבור =, הצד הימני).

  • goto LABEL אינו יכול לצאת מבלוק השוואה של sort. ההשוואה רצה בהקשר שאינו מאפשר את הקפיצה.

  • goto &NAME משליך קישורי local מתת־השגרה היוצאת לפני שהיעד רץ. כל מה שתת־השגרה תיחמה ל־local משוחזר כאילו תת־השגרה חזרה רגיל.

  • caller אינו נראה מעבר ל־goto &. המסגרת היוצאת נעלמה. זוהי כל הנקודה של הצורה עבור AUTOLOAD - אין להסתמך על ראיית תת־השגרה הבינונית ב־stack trace.

  • aliasing של @_ נשמר. ה־@_ של תת־השגרה היעד מהווה alias לאותם סקלרים שה־@_ של תת־השגרה היוצאת היווה alias אליהם. השמה ל־$_[0] ביעד עדיין משנה את הארגומנט של הקורא.

  • goto &$coderef דורש coderef אמיתי. מחרוזת שנראית כמו שם תת־שגרה אינה עובדת בצורת ה־& - יש להשתמש ב־goto EXPR (ללא ה־&) לשם כך, או לפענח את השם ל־coderef קודם עם \&{$name}.

  • ל־goto קדימות של השמה. goto "A" . "B" הוא goto "AB", לא (goto "A") . "B".

הבדלים מ־upstream#

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

ראו גם#

  • last - לצאת מהלולאה הפנימית ביותר או מלולאה עוטפת בעלת שם; הכלי הנכון לרוב המקרים של ״לקפוץ מכאן״

  • next - לדלג לאיטרציה הבאה של לולאה, עם תווית אופציונלית

  • redo - להתחיל מחדש את איטרציית הלולאה הנוכחית מבלי להעריך מחדש את התנאי

  • return - לעזוב את תת־השגרה הנוכחית עם ערך; להשוות עם goto &NAME שעוזב את תת־השגרה על־ידי החלפה שלה

  • caller - לבחון את מחסנית הקריאה; שים לב ש־goto & עושה את המסגרת היוצאת בלתי־נראית בפניו