הפניות לתת־שגרה#

הפניה לתת־שגרה היא סקלר המצביע על חתיכת קוד. ברגע שיש לכם אחת, אתם יכולים לאחסן אותה במשתנה, להעביר אותה לתת־שגרה אחרת כ־callback, לשמור טבלה שלהן ל־dispatch, או לקרוא לה. הפניות לקוד הן מה שהופך טבלאות dispatch, callbacks, איטרטורים ו־closures לאפשריים ב־Perl.

שתי דרכים לקבל הפניית קוד#

\&name - קחו הפניה לתת־שגרה בעלת שם:

sub greet { print "hello $_[0]\n" }

my $cref = \&greet;
$cref->('world');      # hello world

ה־sigil & הוא מה שמסמן את greet כתת־שגרה. ה־backslash לפניה לוקח את ההפניה שלה. ללא ה־&, הייתם קוראים לתת־השגרה ולוקחים הפניה לתוצאה.

sub { ... } - תת־שגרה אנונימית. היא מוערכת להפניה לחתיכת קוד רעננה וחסרת שם:

my $cref = sub { print "hello $_[0]\n" };
$cref->('world');

sub אנונימיים הם המקבילה הישירה של [...] ו־{...} מהפרק הקודם: בנאי שמחזיר לכם הפניה למשהו חדש וחסר שם.

קריאה דרך הפניה#

$cref->(@args) היא זו שאליה אתם פונים:

$cref->('world');
$cref->($x, $y);
$cref->();             # call with no arguments

יש גם תחביר ישן יותר, &{$cref}(...), מקביל לצורה @{$aref} / %{$href}. הוא עדיין תקף אך צורת החץ היא הסטנדרטית בכל מקום מודרני.

בתוך תת־השגרה הנקראת, ארגומנטים מגיעים ב־@_ כרגיל:

my $max = sub {
    my ($a, $b) = @_;
    $a > $b ? $a : $b;
};
print $max->(3, 7);    # 7

Callbacks - העברת קוד לשגרה אחרת#

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

sub each_line {
    my ($filename, $cb) = @_;
    open my $fh, '<', $filename or die "$filename: $!";
    while (my $line = <$fh>) {
        $cb->($line);
    }
}

# Pass a named sub:
sub print_upper { print uc $_[0] }
each_line('notes.txt', \&print_upper);

# Or an anonymous one, inline:
each_line('notes.txt', sub { print uc $_[0] });

sub אנונימיים inline הם הצורה האידיומטית כאשר גוף ה־callback קצר וספציפי לאתר הקריאה.

טבלאות dispatch#

האש של הפניות קוד מחליף שרשרות if / elsif ארוכות:

my %dispatch = (
    help => sub { print "usage: tool [cmd]\n" },
    list => sub { print "$_\n" for @items },
    quit => sub { exit 0 },
);

my $cmd = shift @ARGV;
if (my $action = $dispatch{$cmd}) {
    $action->();
} else {
    die "unknown command: $cmd\n";
}

כל ערך ב־%dispatch הוא הפניית קוד. חיפוש לפי שם פקודה וקריאה דרך ההפניה מחליפים הסתעפות בחיפוש בודד. הוספת פקודה חדשה היא רשומת האש בודדת.

Closures - קוד הלוכד לקסיקלים#

תת־שגרה אנונימית המפנה למשתנה לקסיקלי (my) מהתחום הסובב שלה לוכדת את אותו משתנה. המשתנה שורד כל עוד הפניית תת־השגרה שורדת, וכל קריאה רואה את הערך הנוכחי:

sub make_counter {
    my $n = 0;
    return sub { ++$n };
}

my $a = make_counter();
my $b = make_counter();
print $a->();          # 1
print $a->();          # 2
print $b->();          # 1   - independent from $a
print $a->();          # 3

כל קריאה ל־make_counter יוצרת $n לקסיקלי חדש ותת־שגרה אנונימית חדשה הלוכדת את אותו $n. תת־השגרה המוחזרת ל־$a וזו המוחזרת ל־$b סוגרות על משתנים מובחנים, אז המונים שלהן אינם מפריעים זה לזה.

כך Perl מבטא את רוב מה ששפות אחרות קוראות ״עצמים עם מצב פרטי״ ללא מנגנון מחלקות כלשהו: הלקסיקלים הסגורים הם המצב הפרטי.

closure לוכד בהפניה, לא בהעתק. לולאה כזו לוכדת את אותו משתנה לולאה:

my @subs;
for my $i (1 .. 3) {
    push @subs, sub { print "i=$i\n" };
}
$_->() for @subs;      # i=1 / i=2 / i=3

…מכיוון ש־my $i ב־for my $i מקבל my מחדש בכל איטרציה, אז כל closure לוכד $i שונה. הטעות הנפוצה היא שימוש במשתנה לולאת־for שלא קיבל my רענן:

my $i;                 # outer
my @subs;
for $i (1 .. 3) {
    push @subs, sub { print "i=$i\n" };
}
$_->() for @subs;      # i=3 / i=3 / i=3  - all share the outer $i

התיקון הוא for my $i (...) - תמיד העדיפו את ה־my inline.

אינטרוספקציה#

ref על הפניית קוד מחזיר את המחרוזת CODE:

ref $cref              # CODE
ref \&greet            # CODE

defined עובד כרגיל כדי להבחין בין ״אין ערך״ ל־״כל ערך, כולל הפניית קוד״:

if (defined $dispatch{$cmd}) { ... }

לאן ממשיכים מכאן#

  • הפניות חלשות - רלוונטי כאשר מבנה של הפניות קוד מחזיק הפניות חזרה לעצמים הבעלים שלו (event emitters, שרשרות observer).