Ροή ελέγχου · Αρθρώματα

do#

Εκτελεί ένα μπλοκ κώδικα ή τρέχει ένα αρχείο πηγαίου κώδικα Perl σαν να ήταν τμήμα του τρέχοντος προγράμματος.

Η do φοράει δύο άσχετα καπέλα που μοιράζονται μόνο μια δεσμευμένη λέξη. Το do BLOCK ομαδοποιεί δηλώσεις σε μία ενιαία έκφραση και επιστρέφει την τιμή της τελευταίας - συντακτικό εργαλείο, όχι συνάρτηση. Το do EXPR μεταχειρίζεται τη συμβολοσειρά ως όνομα αρχείου και μεταγλωττίζει-και-εκτελεί το αρχείο σε χρόνο εκτέλεσης. Διαβάστε όποια ενότητα ταιριάζει στη μορφή που βλέπετε· η ανάμειξή τους είναι η συνηθέστερη πηγή σύγχυσης γύρω από αυτή τη δεσμευμένη λέξη.

Σύνοψη#

do BLOCK                        # group statements, yield last value
do EXPR                         # load and run a Perl file
do { ... } while CONDITION;     # post-test loop idiom

Τι επιστρέφεται#

  • do BLOCK - η τιμή της τελευταίας έκφρασης που αποτιμήθηκε στο μπλοκ, σε όποιο περιβάλλον επιβάλλει η περιβάλλουσα έκφραση.

  • do EXPR - η τιμή της τελευταίας έκφρασης που αποτιμήθηκε στο αρχείο σε επιτυχία· undef σε αποτυχία, με την $@ τεθειμένη στο σφάλμα μεταγλώττισης ή την $! τεθειμένη στο σφάλμα I/O.

do BLOCK - ομαδοποίηση δηλώσεων σε μία έκφραση#

Το do BLOCK δεν είναι συνάρτηση. Είναι συντακτική κατασκευή που μετατρέπει μια ακολουθία δηλώσεων οριοθετημένη από αγκύλες σε έναν ενιαίο όρο, του οποίου η τιμή είναι η τιμή της τελευταίας δήλωσης. Καταφύγετε σε αυτή όταν η γλώσσα ζητά έκφραση αλλά έχετε αρκετές δηλώσεις να εκτελέσετε:

my $x = do {
    my $tmp = fetch_raw();
    $tmp =~ s/\s+\z//;
    lc $tmp;
};                              # $x = cleaned, lower-cased value

Όταν το do BLOCK τροποποιείται από επόμενο while ή until, το μπλοκ εκτελείται μία φορά πριν ελεγχθεί η συνθήκη - αυτό είναι το καθιερωμένο ιδίωμα βρόχου με μετα-έλεγχο:

do {
    $line = <$fh>;
} while defined $line && $line !~ /^END\b/;

Σε οποιαδήποτε άλλη δήλωση, οι while και until ως τροποποιητές δήλωσης ελέγχουν τη συνθήκη πρώτα. Η do αποτελεί την εξαίρεση.

Οι next, last, redo δεν λειτουργούν μέσα σε do BLOCK#

Ένα do BLOCK δεν είναι βρόχος, ακόμη και όταν ακολουθείται από while ή until. Οι δεσμευμένες λέξεις ελέγχου βρόχου δεν το βλέπουν:

do {
    last if $done;              # WRONG - not a loop
} while $more;

Αν χρειάζεστε έλεγχο βρόχου, τυλίξτε το σώμα σε γυμνό μπλοκ, που είναι εμβέλεια προσβάσιμη σε βρόχο:

{
    last if $done;
    redo if $retry;
}

Ή χρησιμοποιήστε ρητό βρόχο while.

return μέσα σε γυμνό do BLOCK#

Η return μέσα σε do BLOCK επιστρέφει από την περικλείουσα υπορουτίνα, όχι από το ίδιο το μπλοκ. Αυτό είναι ενίοτε αυτό που θέλετε και ενίοτε σφάλμα - όπως και να έχει, να ξέρετε ποιο από τα δύο γράφετε:

sub classify {
    my $n = shift;
    my $kind = do {
        return "zero" if $n == 0;   # returns from classify(), not the do
        $n > 0 ? "pos" : "neg";
    };
    "$kind ($n)";
}

Για να αποδώσετε μια τιμή από το μπλοκ χωρίς να επιστρέψετε από την υπορουτίνα, αφήστε απλώς την τελευταία έκφραση να είναι η τιμή - αυτό είναι όλο το νόημα του do BLOCK.

do EXPR - φόρτωση και εκτέλεση αρχείου πηγαίου κώδικα Perl#

Το do EXPR αποτιμά την EXPR, μεταχειρίζεται το αποτέλεσμα ως όνομα αρχείου, διαβάζει το αρχείο, το μεταγλωττίζει και το εκτελεί στο τρέχον πακέτο. Είναι παρόμοιο, αλλά όχι ακριβώς ίδιο, με

eval `cat stat.pl`;

Η μορφή do δεν τρέχει εξωτερική διεργασία, διατηρεί το αρχικό όνομα αρχείου για τα μηνύματα σφαλμάτων, και δεν μπορεί να δει λεκτικές μεταβλητές της περικλείουσας εμβέλειας - κάτι που το eval STRING μπορεί. Όπως η eval STRING, το αρχείο επαναναλύεται σε κάθε κλήση, οπότε do FILE μέσα σε βρόχο είναι σχεδόν πάντα λάθος.

Επίλυση διαδρομής και @INC#

Το όνομα αρχείου ερμηνεύεται ως εξής:

  • Απόλυτες διαδρομές (/foo/stat.pl), διαδρομές που ξεκινούν με ./, και διαδρομές που ξεκινούν με ../ χρησιμοποιούνται ως έχουν.

  • Κάθε άλλη σχετική διαδρομή αναζητείται κατά μήκος του @INC, και σε επιτυχία η διαδρομή που βρέθηκε καταγράφεται στο %INC υπό το κλειδί που διαβιβάσατε.

do '/etc/myapp/stat.pl';        # exact file
do './stat.pl';                 # exact file, current directory
do 'stat.pl';                   # search @INC
do 'MyApp/config.pl';           # search @INC

Από την Perl 5.26 και μετά, το . δεν βρίσκεται προεπιλεγμένα στο @INC, άρα ένα γυμνό do 'stat.pl' δεν επαναφέρεται πλέον στον τρέχοντα κατάλογο. Αν το αρχείο δεν βρεθεί, η Perl εκπέμπει την υπόδειξη:

do "stat.pl" failed, '.' is no longer in @INC;
did you mean do "./stat.pl"?

Διαβιβάστε ./stat.pl όταν εννοείτε τον τρέχοντα κατάλογο.

Τιμή επιστροφής και αναφορά σφαλμάτων#

Το do EXPR έχει τρία αποτελέσματα που πρέπει να διακρίνετε:

Αποτέλεσμα

Επιστροφή

$@

$!

Αρχείο μεταγλωττίστηκε και εκτελέστηκε

τιμή της τελευταίας έκφρασης

""

αμετάβλητη

Αρχείο διαβάστηκε αλλά δεν μεταγλωττίστηκε

undef

σφάλμα μεταγλώττισης

ενδέχεται επίσης να τεθεί

Αρχείο δεν μπόρεσε να διαβαστεί

undef

""

σφάλμα I/O

Επειδή η αποτυχία μεταγλώττισης μπορεί παρεμπιπτόντως να θέσει και την $!, ελέγχετε πάντα πρώτα την $@:

my $return;
for my $file ("/etc/myapp.rc", "$ENV{HOME}/.myapprc") {
    unless ($return = do $file) {
        warn "couldn't parse $file: $@" if $@;
        warn "couldn't do $file: $!"    unless defined $return;
        warn "couldn't run $file"       unless $return;
    }
}

Οι τρεις warn αντιστοιχούν στα τρία σχήματα αποτυχίας παραπάνω συν την τέταρτη περίπτωση - το αρχείο εκτελέστηκε αλλά η τελευταία του έκφραση ήταν ψευδής.

Γιατί να μη χρησιμοποιείτε do FILE για αρθρώματα#

Για τη φόρτωση κώδικα βιβλιοθήκης, χρησιμοποιήστε require ή use αντί για do EXPR. Η require αποθηκεύει στην προσωρινή μνήμη μέσω του %INC (ώστε ένα αρχείο να φορτώνεται το πολύ μία φορά), εγείρει εξαίρεση σε αποτυχία αντί να επιστρέφει undef, και ολοκληρώνεται με τους γάντζους του @INC. Η use προσθέτει επιπλέον φόρτωση κατά τη μεταγλώττιση και χειρισμό εισαγωγής. Το do EXPR υπάρχει για αρχεία ρυθμίσεων και ad-hoc ενσωμάτωση σεναρίων - όχι για βιβλιοθήκες.

Παραδείγματα#

Ομαδοποίηση δηλώσεων σε έκφραση:

my $config = do {
    open my $fh, "<", $path or die $!;
    local $/;
    <$fh>;
};                              # $config = whole file as one string

Βρόχος μετα-ελέγχου με do while:

my $n;
do {
    print "enter a positive number: ";
    chomp($n = <STDIN>);
} until defined $n && $n =~ /\A\d+\z/ && $n > 0;

Ανάγνωση αρχείου ρυθμίσεων σε σύνταξη Perl, με πλήρη τριμερή χειρισμό σφαλμάτων:

my $cfg = do '/etc/myapp/config.pl';
if (!defined $cfg) {
    die $@ ? "config parse error: $@"
           : "config read error: $!";
}
die "config did not return a true value" unless $cfg;

Υπό συνθήκη επιλογή τιμής όπου κάθε διακλάδωση αποτελείται από αρκετές δηλώσεις:

my $greeting = do {
    if ($user) {
        my $name = $user->display_name;
        "hello, $name";
    }
    else {
        "hello, stranger";
    }
};

Προσωρινή τοπικοποίηση που πρέπει να καλύπτει αρκετές δηλώσεις και να αποδίδει και τιμή:

my $dump = do {
    local $Data::Dumper::Sortkeys = 1;
    local $Data::Dumper::Indent   = 1;
    Data::Dumper::Dumper(\%state);
};

Καθολική κατάσταση που επηρεάζει#

  • @INC - αναζητείται όταν η do EXPR καλείται με σχετική διαδρομή διαφορετική από ./… ή ../….

  • %INC - ενημερώνεται με τη διαδρομή που επιλύθηκε όταν η do EXPR πετυχαίνει σε αναζήτηση στο @INC.

  • $@ - τίθεται από την do EXPR σε αποτυχία μεταγλώττισης· καθαρίζεται σε επιτυχία.

  • $! - τίθεται από την do EXPR σε αποτυχία I/O (το αρχείο δεν μπόρεσε να διαβαστεί).

  • $0, __FILE__, __LINE__ - το τρέχον υπό μεταγλώττιση όνομα αρχείου τίθεται στο αρχείο που φορτώνεται όσο η do EXPR το αναλύει, ώστε οι προειδοποιήσεις και τα μηνύματα die να δείχνουν στο σωστό αρχείο.

Το do BLOCK δεν επηρεάζει καθολικές μεταβλητές· είναι καθαρά συντακτική κατασκευή.

Οριακές περιπτώσεις#

  • Το do BLOCK δεν είναι βρόχος. Οι next, last και redo μέσα σε do while δεν αφορούν το μπλοκ. Χρησιμοποιήστε γυμνό μπλοκ ή πραγματικό βρόχο while.

  • Η return μέσα σε do BLOCK επιστρέφει από την περικλείουσα υπορουτίνα. Όχι από το μπλοκ. Δεν υπάρχει τρόπος να επιστραφεί τιμή από do BLOCK πέρα από το να γίνει η τελευταία αποτιμούμενη έκφραση.

  • Οι τροποποιητές δήλωσης ελέγχουν εκ των προτέρων παντού αλλού. Το STATEMENT while COND ελέγχει πρώτα· το do BLOCK while COND ελέγχει μετά την πρώτη εκτέλεση. Αυτή είναι σκόπιμη εξαίρεση της γλώσσας.

  • Σχετικές διαδρομές χωρίς ./ αναζητούν στο @INC. Από την 5.26, το . δεν είναι στο @INC, οπότε do 'stat.pl' δεν σημαίνει πια «αρχείο τρέχοντος καταλόγου»· διαβιβάστε ./stat.pl αν αυτή ήταν η πρόθεση.

  • Το do EXPR δεν μπορεί να δει περικλείουσες λεκτικές μεταβλητές. Το αρχείο αναλύεται σαν να βρίσκεται στο ανώτατο επίπεδο. Αν χρειάζεστε πρόσβαση σε λεκτικές μεταβλητές, χρησιμοποιήστε eval STRING - με τις συνήθεις επιφυλάξεις περί taint και injection.

  • Το αρχείο επαναναλύεται σε κάθε κλήση. Η do EXPR δεν αποθηκεύεται προσωρινά μέσω του %INC με τον τρόπο που είναι η require. Κάλεσμα μέσα σε βρόχο επαναδιαβάζει και επαναμεταγλωττίζει το αρχείο σε κάθε επανάληψη.

  • Ελέγχετε την $@ πριν την $!. Μια αποτυχία μεταγλώττισης μπορεί επίσης να θέσει την $! ως παρενέργεια· το σφάλμα μεταγλώττισης στην $@ είναι αυτό που πραγματικά θέλετε να αναφέρετε.

  • Ένα αρχείο που μεταγλωττίζεται αλλά επιστρέφει ψευδή τιμή είναι αμφίσημο. Το unless ($ret = do $file) { } πυροδοτείται τόσο για «το αρχείο απέτυχε» όσο και για «το αρχείο εκτελέστηκε και επέστρεψε 0». Αν επιτρέπεται το αρχείο ρυθμίσεών σας να καταλήγει σε ψευδή τιμή, χρησιμοποιήστε ρητά την defined και την $@ αντί να ελέγχετε την αλήθεια της επιστροφής.

  • do χωρίς όρισμα είναι συντακτικό σφάλμα. Και οι δύο μορφές απαιτούν έναν όρο: ένα μπλοκ ή μια έκφραση.

Διαφορές από το upstream#

Πλήρως συμβατό με το upstream Perl 5.42.

Δείτε επίσης#

  • eval - η eval BLOCK παγιδεύει εξαιρέσεις χωρίς να διαβάζει αρχείο· η eval STRING αναλύει αυθαίρετο κώδικα και μπορεί να δει περικλείουσες λεκτικές μεταβλητές, σε αντίθεση με την do EXPR

  • require - φορτώνει αρχείο βιβλιοθήκης με προσωρινή αποθήκευση μέσω %INC και αυτόματη εξαίρεση σε αποτυχία· το σωστό εργαλείο για αρθρώματα

  • return - σημειώστε ότι η return μέσα σε do BLOCK επιστρέφει από την περικλείουσα υπορουτίνα, όχι από το μπλοκ

  • @INC - διαδρομή αναζήτησης αρθρωμάτων που συμβουλεύεται η do EXPR σε σχετικά ονόματα αρχείων

  • %INC - καταγράφει τη διαδρομή που επιλύθηκε μετά από μια επιτυχημένη do EXPR με αναζήτηση στο @INC

  • $@ - σφάλμα κατά τη μεταγλώττιση από do EXPR

  • $! - σφάλμα I/O από do EXPR όταν το αρχείο δεν μπορεί να διαβαστεί