תת־שגרות lvalue והקשר#

שני נושאים קשורים העולים בכל מקום שבו תת־שגרה צריכה להתנהג באופן שונה בהתאם לאופן שבו היא בשימוש: תת־שגרות :lvalue (אלה השמישות בצד שמאל של השמה) ושיגור מונע־wantarray לפי הקשר הקורא.

מדוע הקשר חשוב בתת־שגרות#

כל ביטוי Perl מוערך בהקשר - רשימה, סקלר (עם תת־גוונים: מספרי, מחרוזת, בוליאני), או void - הנכפה על־ידי מה שמקיף אותו. קריאה לתת־שגרה יורשת את ההקשר של מיקומה:

my @r = my_sub();                # list context
my $r = my_sub();                # scalar context
my_sub();                        # void context
print my_sub();                  # list context (print's args are LIST)
my $n = () = my_sub();           # scalar of (a list assignment) - element count

עבור רוב תת־השגרות, זה בלתי נראה - הן מחזירות את אותם נתונים כך או כך והמרת ההקשר האוטומטית של Perl עושה את הדבר הנכון. עבור חלק מתת־השגרות העבודה שונה באמת בהתאם להקשר, ותת־שגרות אלו נהנות מבדיקה מפורשת של wantarray.

wantarray: המתג התלת־כיווני#

sub items {
    if (wantarray) {
        return (1, 2, 3);                  # list context
    }
    elsif (defined wantarray) {
        return 3;                          # scalar context
    }
    else {
        return;                            # void context - caller will
                                           # discard whatever we return
    }
}

שלושת ערכי ההחזרה של wantarray:

ערך החזרה

הקשר הקורא

משמעות מעשית

1 (אמת)

רשימה

החזר את כל הדברים

0 (שקר)

scalar

החזר דבר אחד (למשל מספר)

undef

void

לאף אחד לא אכפת; שקול להחזיר מוקדם

defined wantarray הוא הבדיקה ל“סקלר או רשימה“ (כל דבר שאינו void).

קיצור הדרך של הקשר void#

כשהעבודה יקרה והקורא זרק את התוצאה, החזר מוקדם:

sub expensive_query {
    return unless defined wantarray;       # nothing to compute
    my @rows = $db->select(...);           # actually do the work
    return wantarray ? @rows : scalar @rows;
}

expensive_query();                         # void: returns immediately
my @r = expensive_query();                 # list: full result
my $n = expensive_query();                 # scalar: count

תבנית זו היא הסיבה הטובה ביותר להיוועץ ב־wantarray: הפיכת תת־שגרת ”תמיד בצע את העבודה“ לתת־שגרת ”בצע את העבודה רק אם הקורא רוצה אותה“.

מתי לא להשתמש ב־wantarray#

  • עבור העמסה לנוחות. תת־שגרה המחזירה hashref בהקשר סקלר ו־hash בהקשר רשימה היא מפגע תחזוקה - הקוראים אינם יכולים לדעת מאתר הקריאה מה הם מקבלים. בחר אחד.

  • עבור תת־שגרות שהן בבירור רשימתיות או סקלריות. תת־שגרה בשם count_users צריכה להחזיר מספר; הקוראים אינם מצפים שהיא תתנהג אחרת בהקשר רשימה.

תת־שגרות :lvalue#

תת־שגרת :lvalue היא כזו שערך ההחזרה שלה הוא בעצמו בר־השמה. הגוף חייב להסתיים בביטוי בעל מיקום בזיכרון - בדרך כלל לקסיקל my או אבר של צבר:

sub editable :lvalue {
    my $self = shift;
    $self->{counter};                      # last expression is an lvalue
}

my $obj = { counter => 0 };
bless $obj, 'Counter';

$obj->editable = 42;                       # writes through to $obj->{counter}

הביטוי האחרון של הגוף הוא מה שההשמה כותבת אליו. אם ביטוי זה אינו lvalue (return $x + 1, return scalar @list), ההשמה אסורה ותקבל שגיאה ברורה.

:lvalue הוא לעתים רחוקות הכלי הנכון. המקומות היחידים שבהם הוא משתלם:

  • משתנים קשורים (tied) ועטיפות דמויות־קשורות, שבהן תת־השגרה מתווכת גישה ל־lvalue אמיתי שמתחת.

  • שפות ייעודיות (DSL) שאכן רוצות תחביר־השמה עבור קביעה (למשל $config->host = 'localhost';).

עבור ”קבע תכונה על עצם“, setter פשוט ברור יותר:

sub set_counter {
    my ($self, $value) = @_;
    $self->{counter} = $value;
    return $self;                           # method chaining
}

$obj->set_counter(42);                     # explicit, no surprises

:lvalue ומבנה הגוף#

מכיוון שהביטוי האחרון של הגוף מיוחד, הכללים מגבילים:

sub good :lvalue { $self->{x} }            # OK - last expr is lvalue

sub also_good :lvalue {
    my @scratch = ...;                     # work
    $self->{x};                            # last expr is lvalue
}

sub bad :lvalue {
    return $self->{x};                     # NOT OK - explicit return
                                           # disables :lvalue
}

sub also_bad :lvalue {
    if ($cond) { $self->{x} }
    else       { $self->{y} }              # an `if` block is not an lvalue
}

כלל ה“אין return מפורש“ הוא אותו כלל הגורם ל־prototypes לקיפול־קבועים לעבוד; שניהם מסתמכים על כך שהגוף הוא ביטוי יחיד מפיק־ערך בסופו.

עבור החלופה המודרנית - setter המחזיר $self לצורך שרשור - ראה את מדריך התכנות מונחה־העצמים.

שילוב :lvalue עם הקשר#

תת־שגרת :lvalue אינה יכולה להסתעף בקלות על wantarray, משום שגרסת ה־lvalue של הקריאה אינה מתאימה בדיוק למודל ”מחזיר ערך“. אם ברצונך את שתי ההתנהגויות מתת־שגרה אחת, קבל שאתה בשטח של DSL ותעד את החוזה בקפידה - או פצל לשתי תת־שגרות.

אופרטורים משפיעי־הקשר#

כמה אופרטורים כופים הקשר ספציפי על הארגומנט שלהם; תת־השגרה תראה הקשר זה ללא קשר לאן הביטוי המקיף הולך:

my @r = scalar my_sub();         # scalar() forces scalar context on my_sub
my $n = () = my_sub();           # the inner () = ... is list context;
                                 # outer assignment to $n is scalar of that

הניב () = LIST הוא הדרך הסטנדרטית לכפות הקשר רשימה על קריאה לתת־שגרה ואז לקרוא את המספר. ראה השמה עבור הדיון ברמת האופרטור.

ראו גם#