ארגומנטים ו־@_#

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

כלל ה־aliasing#

sub bump {
    $_[0]++;                     # mutates the caller's variable
}

my $x = 10;
bump($x);
say $x;                          # 11

שינוי $_[0] כותב דרך ה־alias אל כל מה שהקורא העביר. זה מתוכנן - כך chomp(@lines) מצליח להסיר תווי שורה חדשה מכל אבר של @lines ולא מהעתק של @lines.

ה־aliasing משתרע גם על ליטרלים, עם השלכות צפויות:

bump(10);                        # error: Modification of a read-only
                                 # value attempted

הליטרל 10 הוא קבוע. ה־alias מצביע אליו; ניסיון השינוי מת.

ניב הפירוק#

מכיוון שה־aliasing הוא לעתים רחוקות מה שהגוף רוצה, כמעט כל תת־שגרה בסגנון הקלאסי מתחילה באחת משתי התבניות האלה:

# Named arguments (most common)
sub greet {
    my ($name, $greeting) = @_;  # COPY into private lexicals
    $greeting //= 'Hello';
    return "$greeting, $name!";
}

# Method dispatch
sub method_call {
    my $self = shift;            # COPY first arg, leave rest in @_
    my %args = @_;               # remaining args as a hash
    ...
}

לאחר הפירוק, הגוף עובד עם העתקים פרטיים ואינו יכול לשנות בטעות את מצב הקורא. shift ללא ארגומנט בתוך תת־שגרה מקבל כברירת מחדל את @_, ולכן my $self = shift; נמצא בכל מקום ב־Perl מונחה־עצמים.

שימושים לגיטימיים ב־aliasing#

aliasing הוא הכלי הנכון כשאתה אכן רוצה לשנות את מצב הקורא:

# in-place trim
sub trim_inplace {
    for (@_) {
        s/^\s+//;
        s/\s+$//;
    }
}

my @data = ("  hello  ", "  world\n");
trim_inplace(@data);
# @data is now ("hello", "world")

הלולאה for (@_) עושה alias ל־$_ לכל אבר של @_, שהוא בעצמו alias לכל אבר של @data. שינויי ה־s/// עוברים דרך שני קפיצות ה־alias אל המחרוזות המקוריות. ללא ה־aliasing, אותה צורת קוד הייתה דורשת כפליים עבודה.

אותו מנגנון מניע את chomp(@lines), chop(@buf), וכל פונקציית ”עבוד על רשימה ושנה במקום“ שאתה כותב בעצמך. הצהר על הכוונה בשם הפונקציה (_inplace, mutate_, וכדומה) כדי שהקוראים לא יופתעו.

ארגומנטים בעלי שם#

ל־Perl אין תחביר מובנה של פרמטרים בעלי שם בצורה הקלאסית, אך ה־=> (הפסיק השמן) בתוספת השמת hash נותן את אותה תוצאה:

sub configure {
    my %opt = @_;                # ('host' => 'localhost', 'port' => 8080)
    $opt{host} //= 'localhost';
    $opt{port} //= 80;
    ...
}

configure( host => 'example.com', port => 8080 );

הפסיק השמן זהה בסמנטיקה לפסיק רגיל אך מצטט את ה־bareword משמאלו, כך ש־host => ... הוא קיצור של 'host' => .... ראה , עבור האופרטור עצמו.

עבור ארגומנטים בעלי שם מסוג חובה־פלוס־אופציונלי, הצורה הסטנדרטית היא:

sub render {
    my ($template, %opt) = @_;   # one positional, rest as named
    $opt{escape} //= 1;
    ...
}

render('greeting.tt', escape => 0, locale => 'de');

עבור ניב הפרמטרים בעלי השם המודרני הבנוי על חתימות, ראה חתימות.

shift, pop, unshift, push על @_#

@_ הוא מערך אמיתי. אתה יכול לבצע shift מהקדמה, pop מהאחור, לחתוך אותו, לשנות אותו. אף אחד מאלה אינו משנה את מצב הקורא - רק כתיבה דרך $_[N] משנה.

sub describe {
    my $what = shift;            # remove first arg from @_
    my @rest = @_;               # copy what's left
    ...
}

sub method {
    my $self = shift;
    my @args = @_;
    $self->dispatch(@args);
}

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

wantarray והקשר הקריאה#

בתוך תת־שגרה, wantarray אומר לך את ההקשר שבו בוצעה הקריאה:

wantarray

הקשר

צורה נפוצה

1 (אמת)

רשימה

החזר רשימה

0 (שקר)

scalar

החזר סקלר יחיד

undef

void

החזר מוקדם; התוצאה תיזרק

sub items {
    return unless defined wantarray;     # void: nothing to compute
    my @result = compute_items();
    return wantarray ? @result : scalar @result;
}

@_ אומר מה נכנס; wantarray אומר מה מצופה לחזור. הם שני החצאים של תמונת מוסכמת הקריאה. ראה lvalue והקשר עבור הדיון המלא.

מלכודות נפוצות#

  • my ($x) = @_ לעומת my $x = @_. הראשון הוא הקשר רשימה: $x מקבל את הארגומנט הראשון. השני הוא הקשר סקלר: $x מקבל את מספר הארגומנטים. הסוגריים משמאל הם ההבדל; טעויות כאן נפוצות.

    sub one_arg {
        my ($x) = @_;            # $x = first arg
    }
    
    sub count_args {
        my $n = @_;              # $n = number of args
    }
    
  • שינוי $_[N] ”בטעות“. פונקציות כמו chomp, chop, tr///, s/// פועלות על הארגומנט שלהן במקום. chomp($_[0]) משנה את מחרוזת הקורא. אם זה לא מה שאתה רוצה, העתק תחילה.

  • השטחת מערכים ו־hashes. מערכים ו־hashes המועברים כארגומנטים מאבדים את זהותם באתר הקריאה - כולם הופכים לאברים של ה־@_ השטוח היחיד. כדי להעביר מערך ללא השטחה, העבר הפניה:

    takes_array(\@arr);          # one ref argument
    sub takes_array {
        my ($aref) = @_;
        push @$aref, 'extra';
    }
    
  • @_ לאחר goto &sub. ה־@_ הנוכחי מועבר אל היעד. כל מה שכבר ביצעת עליו shift נעלם מנקודת המבט של היעד. זוהי תכונה, לא באג - ראה רקורסיה.

ראו גם#

  • @_ - הערך של המשתנה ב־perlvar, עם כלל ה־aliasing והדיון בהקשר הלולאה.

  • shift, pop - מקבלים כברירת מחדל את @_ בתוך תת־שגרה.

  • חתימות - החלופה המודרנית לטיפול בפרמטרים בעלי־שם־ומיקומיים.

  • ערכי החזרה - מה שהגוף שולח בחזרה.

  • אופרטור הפסיק - => לבניית ארגומנטים בעלי שם.