זרימת בקרה

redo#

אתחול מחדש של האיטרציה הנוכחית של לולאה מבלי לבדוק שוב את התנאי ומבלי להריץ את הבלוק continue.

redo קופץ חזרה לראש גוף הלולאה ומריץ אותו שוב על אותו קלט - התנאי השומר על הלולאה אינו מוערך שוב, $_ אינו מתקדם על־ידי while (<FH>), האיטרטור של foreach אינו מתקדם, וכל בלוק continue { } המצורף ללולאה מדולג. המעבר הנוכחי מתרחש שוב מההתחלה.

תקציר#

redo
redo LABEL
redo EXPR

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

redo אינו מחזיר ערך. הוא אופרטור בקרת־זרימה: הוא מעביר בקרה ולעולם אינו נופל להוראה הבאה. אל תנסו להשתמש בו כערך של ביטוי - בפרט, לא ניתן להשתמש בו כדי להחזיר ערך מ־eval { }, sub { }, או do { }.

לאיזו לולאה הוא מכוון#

ללא ארגומנט, redo מתייחס ללולאה העוטפת הפנימית ביותר. עם LABEL, הוא מאתחל מחדש את הלולאה המתויגת בתווית זו, ומאפשר להגיע מחוץ ללולאות מקוננות:

OUTER: while (...) {
    while (...) {
        redo OUTER;      # restart the while (...) above
    }
}

redo EXPR (התווסף ב־Perl 5.18) מחשב את התווית בזמן ריצה. הביטוי חייב להעריך למחרוזת המציינת תווית בתחום הנוכחי:

my $target = "OUTER";
redo $target;

בלוק חשוף נספר כלולאה שרצה פעם אחת, כך ש־redo בתוך בלוק הופך אותו למבנה חוזר על עצמו:

{
    my $line = <STDIN>;
    redo if $line =~ /^\s*$/;   # skip blank lines, re-read
    print $line;
}

redo מול next מול last#

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

  • last - יציאה מהלולאה לחלוטין. בלוק continue אינו רץ.

  • next - מעבר לאיטרציה הבאה. בלוק continue כן רץ, ואז תנאי הלולאה נבדק שוב.

  • redo - הרצה מחדש של אותה איטרציה. בלוק continue אינו רץ, ותנאי הלולאה אינו נבדק שוב.

ההבדל בנוגע לבלוק continue הוא זה שמכשיל אנשים. אם לולאה משתמשת ב־continue { } להגדלת מונה או קידום סמן, redo לא יקדם אותו - זוהי כל המטרה, אך זה גם כל ה־foot-gun.

דוגמאות#

בקשה מחדש עד שהמשתמש נותן קלט תקף. בלוק ה־continue מדולג, כך ש־$attempts סופר רק איטרציות שהושלמו, וזה בדרך כלל מה שרוצים:

my $attempts = 0;
while (1) {
    print "Enter a positive integer: ";
    my $n = <STDIN>;
    chomp $n;
    redo unless $n =~ /^[0-9]+$/ && $n > 0;
    print "Got $n after $attempts rejected tries.\n";
    last;
} continue {
    $attempts++;
}

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

LINE: while (<STDIN>) {
    while (s|({.*}.*){.*}|$1 |) {}
    s|{.*}| |;
    if (s|{.*| |) {
        my $front = $_;
        while (<STDIN>) {
            if (/}/) {            # end of comment?
                s|^|$front\{|;
                redo LINE;        # re-process the patched line
            }
        }
    }
    print;
}

כיוון ללולאה חיצונית על־פי תווית. redo INNER יאתחל את הלולאה הפנימית על ה־$x הנוכחי; redo OUTER מאתחל את הלולאה החיצונית על ה־$x הנוכחי מבלי לקדם את האיטרטור של foreach:

OUTER: foreach my $x (@xs) {
    INNER: foreach my $y (@ys) {
        redo OUTER if should_restart($x, $y);
    }
}

הפיכת בלוק חשוף ללולאת ניסיון־חוזר. ללא redo בלוק זה רץ בדיוק פעם אחת; עם redo הוא רץ עד שהשליפה מצליחה:

my $tries = 0;
{
    my $ok = try_fetch();
    if (!$ok && ++$tries < 3) {
        sleep 1;
        redo;
    }
}

מקרי קצה#

  • continue אינו רץ. זהו ההבדל המגדיר מ־next. אם הלולאה מסתמכת על continue { } למונים, סמנים, או ניקוי, redo יעקוף את כל זה. או שתעבירו את העבודה לתוך הגוף או שתשתמשו ב־next במקום.

  • תנאי הלולאה אינו נבדק שוב. while (cond) { ... redo; } ימשיך ללולל גם אם cond היה כעת שקר. אתם אחראים להבטיח שמסלול ה־redo יוצא בסופו של דבר דרך last, return, או נפילה מותנית.

  • לולאה אינסופית בקלות. redo ללא שינוי מצב הוא לולאה אינסופית הדוקה - בדרך כלל לא מה שרוצים. תמיד שלבו את redo עם תנאי שבסופו של דבר הופך לשקר, או עם מונה ניסיון־חוזר שמפעיל last.

  • foreach אינו מתקדם. foreach my $x (@xs) { redo } שומר את $x מקובע על האלמנט הנוכחי ומלולל עליו לנצח. האיטרטור מתקדם רק בנפילה או ב־next.

  • while (<FH>) אינו קורא את השורה הבאה. $_ שומר את השורה הנוכחית לרוחב ה־redo, וזו בדיוק הסיבה שמסיר הערות Pascal לעיל עובד.

  • redo אל מחוץ ל־grep / map אינו נתמך. upstream מתעד זאת כלא־מוגדר; אל תשתמשו ב־redo ליציאה או אתחול מחדש של grep BLOCK LIST או map BLOCK LIST.

  • redo אל מחוץ ל־eval, sub, או do אינו return. הוא מבצע בקרת זרימה; אין ערך להחזיר. redo שיצטרך לחצות גבול כזה כדי להגיע ללולאה הוא שגיאת זמן ריצה (״Can’t redo outside a loop block״).

  • מוזרות קדימות. בניגוד לרוב האופרטורים הנקובים בשם, ל־redo יש קדימות של הצבה והוא פטור מכלל ה־״נראה־כמו־פונקציה״. redo ("foo")."bar" מעביר את כל השרשור כארגומנט ל־redo - הסוגריים אינם מבודדים את "foo".

הבדלים מ־upstream#

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

ראו גם#

  • last - יציאה מהלולאה לחלוטין; בלוק continue אינו רץ, זהה ל־redo

  • next - קידום לאיטרציה הבאה; בלוק continue כן רץ ותנאי הלולאה נבדק שוב

  • return - החזרה מהתת־שגרה העוטפת, ולא רק מהלולאה; בניגוד ל־redo, הוא מניב ערך

  • continue - הבלוק ש־redo מדלג עליו אך next מריץ; ליבת ההבחנה התלת־סטרית