Αναφορές υπορουτινών#
Η αναφορά υπορουτίνας είναι ένα βαθμωτό που δείχνει σε ένα κομμάτι κώδικα. Μόλις έχετε μία, μπορείτε να την αποθηκεύσετε σε μεταβλητή, να την περάσετε σε άλλη υπορουτίνα ως callback, να κρατάτε πίνακα από αυτές για αποστολή, ή να την καλέσετε. Οι αναφορές κώδικα είναι αυτό που κάνει πίνακες αποστολής, callbacks, επαναλήπτες, και closures εφικτά στην Perl.
Δύο τρόποι να πάρετε αναφορά κώδικα#
\&name - λήψη αναφοράς σε κατονομασμένη υπορουτίνα:
sub greet { print "hello $_[0]\n" }
my $cref = \&greet;
$cref->('world'); # hello world
Το sigil & είναι αυτό που σημαδεύει την greet ως υπορουτίνα. Η ανάστροφη κάθετος μπροστά παίρνει την αναφορά της. Χωρίς το &, θα καλούσατε την υπορουτίνα και θα παίρνατε αναφορά στο αποτέλεσμα.
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 είναι η ιδιωματική μορφή όταν το σώμα του callback είναι σύντομο και ειδικό για το σημείο κλήσης.
Πίνακες αποστολής#
Ένας κατακερματισμός αναφορών κώδικα αντικαθιστά μεγάλες αλυσίδες 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.
Ενδοσκόπηση#
Η ref σε αναφορά κώδικα επιστρέφει τη συμβολοσειρά CODE:
ref $cref # CODE
ref \&greet # CODE
Η defined λειτουργεί ως συνήθως για να ξεχωρίσει το «καμία τιμή» από το «οποιαδήποτε τιμή, συμπεριλαμβανομένης αναφοράς κώδικα»:
if (defined $dispatch{$cmd}) { ... }
Πού να πάτε στη συνέχεια#
Ασθενείς αναφορές - σχετικές όταν μια δομή αναφορών κώδικα κρατάει αναφορές πίσω σε αντικείμενα που την κατέχουν (εκπομποί συμβάντων, αλυσίδες παρατηρητών).