הקשר#

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

my @arr = (10, 20, 30);

my @copy = @arr;          # list context     - three elements
my $len  = @arr;          # scalar context   - 3
print @arr;               # list context     - "102030"
print "@arr";             # list context inside ""  - "10 20 30"
print "" . @arr;          # scalar context (concat) - "3"
@arr;                     # void context - discarded; warns under -w

הקשר הוא ההבדל הגדול ביותר בין Perl לבין רוב השפות האחרות, והמקור הנפוץ ביותר להפתעות מסוג ”why doesn’t this do what I expect“.

ארבעת ההקשרים#

הקשר

מה האופרנד רואה

נכפה על־ידי

רשימה

”give me all of your values“

@a = ..., (...), ארגומנטים של פונקציה, print

סקלר

”give me one value“

$s = ..., if/while, ., +, השוואה

void

”throw the result away“

הוראה שהערך שלה אינו בשימוש

בוליאני

”give me a true/false“

if, unless, while, &&, ||, !

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

כפיית הקשר#

הצד השמאלי של השמה כופה הקשר על הימני:

my @x = func();           # func() is called in LIST context
my $x = func();           # func() is called in SCALAR context
my ($x, $y) = func();     # func() is called in LIST context
my ($x) = func();         # also LIST - note the parens
my  $x  = func();         # SCALAR - same chars, very different

המקרה שמועדים בו ביותר הוא my $x = func() מול my ($x) = func(). ללא סוגריים, ההשמה היא סקלר; עמם, היא השמת רשימה בת אלמנט אחד. אם func() מחזירה שלושה דברים בהקשר רשימה ו“the count“ בהקשר סקלר, ההבדל הוא בין קבלת 'first-thing' לבין קבלת 3.

גם אופרטורים כופים הקשר. אופרטורים מספריים ומחרוזתיים כופים הקשר סקלר על האופרנדים שלהם; print כופה רשימה:

my @arr = (1, 2, 3);
my $n = @arr + 0;         # scalar context - 3 + 0 = 3
my $s = @arr . "";        # scalar context - "3"
print @arr;               # list context - prints "123"

והבדיקות הבוליאניות כופות הקשר סקלר (בוליאני):

if (@arr)        { ... }  # true if @arr is non-empty
unless (%hash)   { ... }  # true if %hash is empty
while (<$fh>)    { ... }  # context is BOOLEAN, but the diamond op is
                          #   special: it reads ONE line and assigns to $_

מה מערך מניב בהקשר סקלר#

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

my @arr = (1, 2, 3);
my $n = @arr;            # 3

ליטרל רשימה בהקשר סקלר מניב את האלמנט האחרון שלו (אופרטור הפסיק):

my $x = (1, 2, 3);       # 3   - comma operator, last element
my $x = ('a', 'b', 'c'); # 'c'

אלה נראים זהים ומתנהגים באופן שונה. הכלל הוא: למערכים יש אורך, לליטרלי רשימה אין. רשימת־פסיק חשופה היא אופרטור הפסיק של C ומייצרת את האופרנד האחרון שלה; משתנה מערך במיקום סקלר שואל ”how many“:

my @a = ('x', 'y', 'z');
my $count   = @a;        # 3
my $last_el = $a[-1];    # 'z'

האופרטור scalar כופה הקשר סקלר על כל מה שבא אחריו, מה ששימושי ביותר כאשר מיקום הביטוי דו־משמעי אחרת:

print "got " . scalar(@arr) . " items\n";     # "got 3 items"
print "got " . @arr . " items\n";              # same - concat imposes scalar
print "got @arr items\n";                      # "got 1 2 3 items" - list inside ""

מה האש מניב בהקשר סקלר#

האש לא־ריק בהקשר סקלר מחזיר ערך אמת (מספר המפתחות, מאז Perl 5.25); האש ריק מחזיר את ערך השקר 0. הצורה שחשובה היא ערך האמת:

if (%h) { ... }         # true if %h has any pairs
my $n = keys %h;        # explicit: count of keys
my $n = %h;             # also count of keys (Perl 5.25+); was a debug ratio earlier

בקוד ישן יותר המכוון ל־perls שלפני 5.25 עדיין רואים לעיתים scalar keys %h; ב־PetaPerl (יעד perl 5.42) scalar %h מחזיר את מספר המפתחות וזה בסדר.

wantarray - כתבו subs רגישי הקשר#

sub יכול לשאול ”what context was I called in?“ עם wantarray:

sub items {
    return wantarray ? (1, 2, 3)        # list context
         : defined wantarray ? 3        # scalar context
         : do { warn "items() called in void context\n"; () };  # void context
}

my @list = items();       # (1, 2, 3)
my $cnt  = items();       # 3
items();                  # void - warning fires

ההבחנה התלת־כיוונית היא הדרך היחידה לזהות void מתוך sub. השתמשו בה במשורה: רוב ה־subs מחזירים צורה אחת ומשאירים לקורא לדאוג להמרת ההקשר. המקומות שבהם wantarray מצדיק את קיומו הם:

  • פונקציות המחזירות לגיטימית או את ”the list“ או את ”the count“ - למשל שגרות חיפוש טקסט.

  • עוזרי אבחון המדפיסים אזהרה כשהתוצאה שלהם הושלכה.

sub המחזיר משמעויות שונות (לא רק צורות שונות) על בסיס wantarray הוא סכנת תחזוקה. שתי פונקציות בעלות שם נקראות טוב יותר מאחת פולימורפית.

מלכודת: scalar(@arr) מול @arr במיקומים שונים#

my @arr = (1, 2, 3);

# 1. Function arguments - LIST context
some_func(@arr);          # passes 1, 2, 3 as three arguments
some_func(scalar @arr);   # passes 3 as a single argument

# 2. String concatenation - SCALAR context
"got " . @arr             # "got 3"

# 3. Inside double quotes - array interpolation, NOT scalar!
"got @arr"                # "got 1 2 3"

# 4. Boolean test - SCALAR (boolean) context
if (@arr) { ... }         # true iff @arr non-empty

# 5. Comparison - SCALAR context on both sides
@arr == 3                 # 3 == 3 is true
@arr eq "3"               # "3" eq "3" is true; this is rarely what you want

אי־ההתאמה בין מקרה 2 (שרשור → סקלר) למקרה 3 (בתוך "" → רשימה) היא שאלת הקשר Perl הנשאלת ביותר. בתוך "", @arr הוא שיבוץ מערך; מחוץ ל־"", הקשר הביטוי המקיף מכריע.

מלכודת: השטחת־רשימה בארגומנטים של פונקציה#

פונקציה מקבלת את @_ כרשימה שטוחה יחידה של כל הארגומנטים שלה - אין דרך לנקרא לדעת היכן מערך אחד הסתיים והבא החל:

sub many {
    print "got ", scalar @_, " args\n";
}

my @a = (1, 2);
my @b = (3, 4, 5);

many(@a, @b);             # 5 args - both arrays flattened
many(\@a, \@b);           # 2 args - two array references
many(scalar @a, scalar @b); # 2 args - two integers (the lengths)

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

דוגמה אמיתית: sub שמחזיר ספירה או רשימה#

sub digits_of {
    my ($s) = @_;
    return $s =~ /\d/g;     # in list context: all digit chars
                            # in scalar context: TRUE/FALSE (last match)
}

my @digits = digits_of("a1b2c3");      # ('1', '2', '3')   - three matches
my $any    = digits_of("a1b2c3");      # 1                  - boolean

כדי לגרום ל־digits_of להחזיר ספירה בהקשר סקלר, הצד הימני חייב להמיר במפורש:

sub digits_of {
    my ($s) = @_;
    my @d = $s =~ /\d/g;
    return wantarray ? @d : scalar @d;
}

my @digits = digits_of("a1b2c3");      # ('1', '2', '3')
my $n      = digits_of("a1b2c3");      # 3

זהו הניב הקנוני של ”return a list or its length“. שימו לב שללא מתג ה־wantarray, return @d עדיין היה נותן את הספירה בהקשר סקלר (בגלל כלל המערך־בסקלר)

  • אך ברור יותר להיות מפורשים.

ראו גם#

  • מערכים - הטריכוטומיה מערך־מול־רשימה־מול־סקלר מורחבת.

  • האשים - %h בהקשר סקלר.

  • פסיק - האופרטור הבונה ליטרלי רשימה ומניב את האופרנד האחרון שלו בהקשר סקלר.

  • wantarray - הדרך לשאול מתוך ה־sub.

  • scalar - מאלץ הקשר סקלר.

  • reverse - רגיש להקשר: רשימה של סקלרים בהקשר רשימה, מחרוזת הפוכה יחידה בהקשר סקלר.

  • הפניות - העברת צורה לא־שטוחה דרך מוסכמת ארגומנטי הקשר־הרשימה המשטיחה.