הפניות#

הפניה היא סקלר שמצביע על ערך אחר. הפניות הן הדרך שבה Perl בונה מבני נתונים מקוננים - מערכים של מערכים, האש של האשים, האש של מערכים של האשים - משום שטיפוסי המכל הבסיסיים (מערכים, האשים) משתטחים כשהם מופיעים ברשימות, והפניה היא סקלר יחיד ששורד את ההשתטחות.

my @arr = (1, 2, 3);
my $ref = \@arr;             # take a reference to @arr
$$ref[0]                     # full deref form     - 1
$ref->[0]                    # arrow shorthand     - 1
@$ref                        # whole-array deref   - (1, 2, 3)

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

דברים שניתן להפנות אליהם#

שבעה דברים מקבלים הפניה:

\$scalar             # SCALAR ref
\@array              # ARRAY ref
\%hash               # HASH ref
\&subroutine         # CODE ref
\*glob               # GLOB ref
\\$scalar            # REF (a reference to a reference)
\$file_handle        # IO ref (reference to a filehandle)

בתוספת הבנאים האנונימיים, הבונים את הערך ומחזירים הפניה בצעד אחד:

my $aref = [1, 2, 3];                  # anonymous array
my $href = { name => 'Alice', age => 30 };  # anonymous hash
my $cref = sub { $_[0] * 2 };          # anonymous sub

ההבחנה בין סוגריים־מרובעים לעגולים מהותית: (1, 2, 3) היא רשימה, [1, 2, 3] היא הפניית מערך אנונימית. הראשונה משתטחת; השנייה אינה.

Dereferencing#

ארבעה כתיבים, בסדר עולה של תדירות הופעתם בקוד מודרני:

${$sref}             # full sigil-deref form
$$sref               # short sigil-deref form (only if name is simple)
$sref->{key}         # arrow form (postfix; for arrays and hashes)
$sref->[0]           # same, for arrays

צורת חץ ה־-> היא הכתיב הניבי עבור גישה מקוננת:

$config->{database}{host}            # arrow elision between subscripts
$config->{database}->{host}          # explicit; same meaning
${$config->{database}}{host}         # full form; the same again

בין שני מנויים עוקבים ה־-> הוא אופציונלי (כלל ה“arrow elision“); בראש שרשרת הוא נדרש:

$ref->[0]                     # required arrow at the head
$ref->[0][1]                  # second arrow elided
$ref->[0]->[1]                # equivalent
[1,2,3]->[0]                  # required arrow on an anonymous ref

deref של מכל־כולו:

@$aref                        # the whole array
%$href                        # the whole hash, as flat key/value list
&$cref(@args)                 # call the sub
$cref->(@args)                # idiomatic call form

השתמשו בצורת הסוגריים־המסולסלים @{ EXPR } / %{ EXPR } כאשר מטרת ה־deref היא ביטוי מורכב יותר ממשתנה סקלרי חשוף:

my @found = @{ $config->{users} };   # deref a chain - needs braces

ראו חץ עבור האופרטור ו־מנוי עבור משפחת הסוגריים.

קדימות של דה-רפרנס בתחילית מול אינדקס#

חמשת סימני הדה-רפרנס בתחילית $ @ * % & נקשרים חזק יותר מסוגריי האינדקס בסופית [] ו-{}. כך ששני אלה הם בדיוק אותו הביטוי:

$$aref[2][2]                  # confusing, but valid
$aref->[2][2]                 # clear; identical meaning

$$aref[2][2] מבצע דה-רפרנס ל-$aref תחילה, ואז מאנדקס את התוצאה. זו אינה הקריאה של C *a[i], שבה האינדקס היה חל לפני הדה-רפרנס. בפרל אין מבנה שמאנדקס לפני דה-רפרנס באותו מיקום; אם רצונך ב“דבר שאליו מצביע האיבר ה-$i“, כתוב זאת משמאל לימין באמצעות דה-רפרנס בסופית:

$aref->[$i]->$*               # deref the i-th element explicitly

צורת החץ היא הבחירה הקריאה לגישה מקוננת; צורת הסימן המוביל היא מלכודת דווקא משום שהיא נקראת כקדימות של C ומשמעותה הפוכה. ראה precedence לטבלת האופרטורים המלאה.

מה ref מחזיר#

ref מדווח על סוג הדבר שהפניה מצביעה עליו:

ref \$scalar         # 'SCALAR'
ref \@array          # 'ARRAY'
ref \%hash           # 'HASH'
ref \&sub            # 'CODE'
ref \*FH             # 'GLOB'
ref \\$scalar        # 'REF'    - a ref-to-ref
ref \$io_handle      # 'IO'     - wrapper around a file handle

ref [1, 2, 3]        # 'ARRAY'
ref { a => 1 }       # 'HASH'
ref sub {}           # 'CODE'

ref bless {}, 'Foo'  # 'Foo'    - blessed; class name not 'HASH'
ref 42               # ''       - not a reference at all
ref undef            # ''

עבור עצמים blessed, ref מחזיר את המחלקה. כדי להבחין בין hash-ref שעבר bless לבין hash-ref רגיל, השתמשו ב־Scalar::Util::reftype (המחזיר תמיד את צורת המכל הבסיסית ללא תלות ב־bless) או ב־isa (הבדיקה המודעת לירושה).

Dereference פוסטפיקסי (@*, %*, $*)#

Perl 5.20+ הוסיף תחביר פוסטפיקסי המשקף את שרשרת החץ:

$ref->@*               # equivalent to @$ref
$ref->%*               # equivalent to %$ref
$ref->$*               # equivalent to $$ref

$ref->@[1, 3]          # array slice through ref - equivalent to @{$ref}[1,3]
$ref->%[1, 3]          # key/value slice
$ref->@{qw(a b)}       # hash slice

הצורות הפוסטפיקסיות נקראות משמאל־לימין עם שאר שרשרת החץ, מה שלעיתים ברור יותר כאשר מטרת ה־deref היא בעצמה שרשרת:

$config->{users}->@*             # all users - chains naturally
@{$config->{users}}              # same; reads right-to-left

בחרו את הצורה שנקראת טוב יותר בהקשר. פוסטפיקס הוא הבחירה הברורה יותר בשרשראות; צורת ה־sigil המוביל קצרה יותר עבור הפניות סקלר רגילות.

Autovivification#

קריאה או כתיבה דרך deref לחריץ שעדיין אינו קיים יוצרת אוטומטית את המבנה הביניימי:

my %h;
$h{a}{b}{c} = 1;
# %h is now (a => { b => { c => 1 } })  - three nested hashes built

המנגנון: כאשר מטרת ההשמה היא $h{a}{b}{c}, Perl רואה ש־$h{a} הוא undef, המנוי הבא הוא {b} (מנוי האש), ולכן הוא הופך את $h{a} ל־hash ref חדש. אז $h{a}{b} הוא undef, המנוי הבא הוא {c} (גם האש), ולכן הופכים אותו ל־hash ref חדש. אז משימים 1 לעלה.

אותו דבר קורה עבור מנויי מערך:

my %by_dept;
push @{ $by_dept{eng} }, 'Alice';
# $by_dept{eng} did not exist; the push autoviv'd it as an arrayref

Autoviv בצד שמאל של השמה (או מוטטור push/pop/…) הוא מה שגורם לזה לעבוד. Autoviv בצד ימין של ביטוי מסוכן יותר - קריאת $h{a}{b}{c} כאשר $h{a} הוא undef גם עושה autovivify, מאכלסת את ההאש כתופעת לוואי של בחינה:

my %h;
exists $h{a}{b};         # accidentally creates $h{a} = {} as a hash!
                          # %h is now (a => {})

התיקון כשאתם באמת רק רוצים לבדוק: שרשרו exists:

exists $h{a} && exists $h{a}{b};        # short-circuits if $h{a} absent

זוהי מלכודת ה־autoviv המנשכת ביותר. בדיקות של קיום מקונן חייבות לקצר, אחרת הן משאירות מאחור חריצי האש.

האש של מערכים - הדוגמה הקנונית#

my @people = (
    { name => 'Alice', dept => 'eng'   },
    { name => 'Bob',   dept => 'sales' },
    { name => 'Carol', dept => 'eng'   },
    { name => 'Dan',   dept => 'sales' },
);

# Group by department:
my %by_dept;
for my $p (@people) {
    push @{ $by_dept{ $p->{dept} } }, $p->{name};
}
# %by_dept = (
#     eng   => ['Alice', 'Carol'],
#     sales => ['Bob', 'Dan'],
# )

# Iterate:
for my $dept (sort keys %by_dept) {
    print "$dept: @{ $by_dept{$dept} }\n";
}
# eng: Alice Carol
# sales: Bob Dan

ה־@{ $by_dept{...} } הוא צורת ה־deref־עם־סוגריים. הוא נחוץ משום שמטרת ה־deref היא ביטוי ($by_dept{$dept}), לא משתנה חשוף.

אותה צורה עם הצורה הפוסטפיקסית:

print "$dept: ", join(' ', $by_dept{$dept}->@*), "\n";

בחרו את מה שתמצאו ברור יותר; שניהם ניביים ב־Perl מודרני.

הפניות חלשות ומבנים מעגליים#

הפניה מחזיקה את הישות המוצבעת בחיים (refcount מוגדל) עד שההפניה עצמה יוצאת מהתחום. זה הופך לבעיה כאשר שני מבנים מצביעים זה על זה:

my $parent = { name => 'p' };
my $child  = { name => 'c', parent => $parent };
push @{ $parent->{children} }, $child;

# parent refers to child via the children arrayref
# child refers back to parent via the parent slot
# When both go out of scope, neither's refcount can reach 0 - leak.

התיקון הוא הפניה חלשה - הפניה שאינה מגדילה את ה־refcount. חריץ ה־$child->{parent} הוא זה שיש להחליש (המצביע־לאחור):

use Scalar::Util qw(weaken);

my $parent = { name => 'p' };
my $child  = { name => 'c', parent => $parent };
weaken $child->{parent};                 # break the cycle
push @{ $parent->{children} }, $child;

לאחר ההחלשה, $child->{parent} נקרא כהורה כל עוד הוא קיים, והופך בשקט ל־undef כשההורה משוחרר. המקום הקנוני עבור weaken הוא על כל מצביע־לאחור במבנה עץ שבו ילדים מצביעים בחזרה אל ההורה שלהם.

הפרדיקט Scalar::Util::isweak בודק אם הפניה נתונה הוחלשה.

שוויון הפניות#

שתי הפניות שוות (מספרית ==, מחרוזתית eq) אם ורק אם הן מתייחסות לאותו ערך בסיסי:

my @a = (1, 2, 3);
my $r1 = \@a;
my $r2 = \@a;
my $r3 = [1, 2, 3];      # different anonymous array

$r1 == $r2               # TRUE - same address
$r1 == $r3               # FALSE - different array, even though contents match

השוואה מספרית של הפניות משווה למעשה את כתובת המכונה שלהן. שתי הפניות עם אותו תוכן אך זהויות שונות משתוות כלא־שוות. שוויון־עמוק זקוק לעוזר מ־Test::More או לסורק מגולגל־ביד.

הפניה מומרת למחרוזת לדבר־מה כמו ARRAY(0x55ab12cd) - שם מחלקה בתוספת כתובת הקסדצימלית. אל תסתמכו על הפורמט; השתמשו בו רק כהדפסת ניפוי.

ראו גם#