Μια εύκολη εισαγωγή στο Lexical Scoping σε JavaScript

Το Lexical scoping είναι ένα θέμα που τρομάζει πολλούς προγραμματιστές. Μία από τις καλύτερες εξηγήσεις για τη λεξική κάλυψη μπορεί να βρεθεί στο βιβλίο του Kyle Simpson You Don't Know JS: Scope and Closures. Ωστόσο, ακόμη και η εξήγησή του λείπει επειδή δεν χρησιμοποιεί ένα πραγματικό παράδειγμα.

Ένα από τα καλύτερα αληθινά παραδείγματα για το πώς λειτουργεί το λεξικό scoping, και γιατί είναι σημαντικό, μπορεί να βρεθεί στο διάσημο βιβλίο, «Η δομή και η ερμηνεία των προγραμμάτων υπολογιστών» (SICP) από τους Harold Abelson και Gerald Jay Sussman. Ακολουθεί ένας σύνδεσμος για μια έκδοση PDF του βιβλίου: SICP.

Το SICP χρησιμοποιεί το Σχέδιο, μια διάλεκτο του Lisp, και θεωρείται ένα από τα καλύτερα εισαγωγικά κείμενα της επιστήμης των υπολογιστών που έχουν γραφτεί ποτέ. Σε αυτό το άρθρο, θα ήθελα να επανεξετάσω το παράδειγμα του λεξικού scoping χρησιμοποιώντας το JavaScript ως γλώσσα προγραμματισμού.

Το παράδειγμά μας

Το παράδειγμα που χρησιμοποίησαν οι Abelson και Sussman είναι ο υπολογισμός τετραγωνικών ριζών χρησιμοποιώντας τη μέθοδο του Νεύτωνα. Η μέθοδος του Νεύτωνα λειτουργεί καθορίζοντας διαδοχικές προσεγγίσεις για την τετραγωνική ρίζα ενός αριθμού έως ότου η προσέγγιση φτάσει εντός ενός ορίου ανοχής για να είναι αποδεκτή. Ας δούμε ένα παράδειγμα, όπως κάνουν οι Abelson και Sussman στο SICP.

Το παράδειγμα που χρησιμοποιούν είναι να βρουν την τετραγωνική ρίζα του 2. Ξεκινάτε να μαντέψετε στην τετραγωνική ρίζα του 2, ας πούμε 1. Βελτιώνετε αυτήν την εικασία διαιρώντας τον αρχικό αριθμό με την εικασία και, στη συνέχεια, ο μέσος όρος αυτού του πηλίκου και η τρέχουσα εικασία ελάτε με την επόμενη εκτίμηση Σταματάτε όταν φτάσετε σε αποδεκτό επίπεδο προσέγγισης. Οι Abelson και Sussman χρησιμοποιούν την τιμή 0,001. Ακολουθεί μια συνέχεια των πρώτων βημάτων στον υπολογισμό:

Square root to find: 2First guess: 1Quotient: 2 / 1 = 2Average: (2+1) / 2 = 1.5Next guess: 1.5Quotient: 1.5 / 2 = 1.3333Average: (1.3333 + 1.5) / 2 = 1.4167Next guess: 1.4167Quotient: 1.4167 / 2 = 1.4118Average: (1.4167 + 1.4118) / 2 = 1.4142

Και ούτω καθεξής έως ότου η εικασία φτάσει στο όριο προσέγγισής μας, το οποίο για αυτόν τον αλγόριθμο είναι 0,001.

Μια συνάρτηση JavaScript για τη μέθοδο του Νεύτωνα

Μετά από αυτήν την επίδειξη της μεθόδου, οι συγγραφείς περιγράφουν μια γενική διαδικασία για την επίλυση αυτού του προβλήματος στο Σχέδιο. Αντί να σας δείξω τον κωδικό σχήματος, θα τον γράψω σε JavaScript:

function sqrt_iter(guess, x) { if (isGoodEnough(guess, x)) { return guess; } else { return sqrt_iter(improve(guess, x), x); }}

Στη συνέχεια, πρέπει να ολοκληρώσουμε πολλές άλλες λειτουργίες, όπως το isGoodEnough () και να βελτιώσουμε (), μαζί με κάποιες άλλες λειτουργίες βοηθού. Θα ξεκινήσουμε με βελτίωση (). Εδώ είναι ο ορισμός:

function improve(guess, x) { return average(guess, (x / guess));}

Αυτή η συνάρτηση χρησιμοποιεί έναν βοηθητικό μέσο όρο (). Εδώ είναι αυτός ο ορισμός:

function average(x, y) { return (x+y) / 2;}

Τώρα είμαστε έτοιμοι να ορίσουμε τη συνάρτηση isGoodEnough (). Αυτή η συνάρτηση χρησιμεύει για να προσδιορίσει πότε η εικασία μας είναι αρκετά κοντά στην ανοχή προσέγγισής μας (0,001). Εδώ είναι ο ορισμός του isGoodEnough ():

function isGoodEnough(guess, x) { return (Math.abs(square(guess) - x)) < 0.001;}

Αυτή η συνάρτηση χρησιμοποιεί μια συνάρτηση τετραγώνου (), η οποία είναι εύκολο να προσδιοριστεί:

function square(x) { return x * x;}

Τώρα το μόνο που χρειαζόμαστε είναι μια λειτουργία για να ξεκινήσουμε τα πράγματα:

function sqrt(x) { return sqrt_iter(1.0, x);}

Αυτή η συνάρτηση χρησιμοποιεί το 1.0 ως αρχική εικασία, η οποία είναι συνήθως μια χαρά.

Τώρα είμαστε έτοιμοι να δοκιμάσουμε τις λειτουργίες μας για να δούμε αν λειτουργούν. Τους φορτώνουμε σε ένα κέλυφος JS και στη συνέχεια υπολογίζουμε μερικές τετραγωνικές ρίζες:

> .load sqrt_iter.js> sqrt(3)1.7321428571428572> sqrt(9)3.00009155413138> sqrt(94 + 87)13.453624188555612> sqrt(144)12.000000012408687

Οι λειτουργίες φαίνεται να λειτουργούν καλά. Ωστόσο, υπάρχει μια καλύτερη ιδέα που κρύβεται εδώ. Όλες αυτές οι λειτουργίες γράφονται ανεξάρτητα, παρόλο που προορίζονται να συνεργάζονται μεταξύ τους. Πιθανότατα δεν πρόκειται να χρησιμοποιήσουμε τη συνάρτηση isGoodEnough () με οποιοδήποτε άλλο σύνολο λειτουργιών ή μόνο του. Επίσης, η μόνη συνάρτηση που έχει σημασία για τον χρήστη είναι η συνάρτηση sqrt (), καθώς αυτή είναι που καλείται να βρει μια τετραγωνική ρίζα.

Block Scoping Hides Helper Functions

Η λύση εδώ είναι να χρησιμοποιήσετε το block scoping για να ορίσετε όλες τις απαραίτητες βοηθητικές λειτουργίες στο μπλοκ της συνάρτησης sqrt (). Θα αφαιρέσουμε το τετράγωνο () και το μέσο όρο () από τον ορισμό, καθώς αυτές οι συναρτήσεις μπορεί να είναι χρήσιμες σε άλλους ορισμούς συναρτήσεων και δεν περιορίζονται στη χρήση σε έναν αλγόριθμο που εφαρμόζει τη μέθοδο Newton. Εδώ είναι ο ορισμός της συνάρτησης sqrt () με τις άλλες λειτουργίες βοηθού που ορίζονται στο πεδίο εφαρμογής του sqrt ():

function sqrt(x) { function improve(guess, x) { return average(guess, (x / guess)); } function isGoodEnough(guess, x) { return (Math.abs(square(guess) - x)) > 0.001; } function sqrt_iter(guess, x) { if (isGoodEnough(guess, x)) { return guess; } else { return sqrt_iter(improve(guess, x), x); } } return sqrt_iter(1.0, x);}

Τώρα μπορούμε να φορτώσουμε αυτό το πρόγραμμα στο κέλυφος μας και να υπολογίσουμε μερικές τετραγωνικές ρίζες:

> .load sqrt_iter.js> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> sqrt(3.14159)1.772581833543688> sqrt(144)12.000000012408687

Σημειώστε ότι δεν μπορείτε να καλέσετε καμία από τις λειτουργίες βοηθού εκτός της συνάρτησης sqrt ():

> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> improve(1,2)ReferenceError: improve is not defined> isGoodEnough(1.414, 2)ReferenceError: isGoodEnough is not defined

Δεδομένου ότι οι ορισμοί αυτών των συναρτήσεων (βελτίωση () και isGoodEnough ()) έχουν μετακινηθεί εντός του πεδίου του sqrt (), δεν είναι δυνατή η πρόσβαση σε υψηλότερο επίπεδο. Φυσικά, μπορείτε να μετακινήσετε οποιονδήποτε από τους ορισμούς της συνάρτησης βοηθού εκτός της συνάρτησης sqrt () για να έχετε πρόσβαση σε αυτούς παγκοσμίως, όπως κάναμε με το μέσο όρο () και το τετράγωνο ().

Έχουμε βελτιώσει σημαντικά την εφαρμογή της μεθόδου του Νεύτωνα, αλλά υπάρχει ακόμα ένα ακόμη πράγμα που μπορούμε να κάνουμε για να βελτιώσουμε τη λειτουργία sqrt () απλοποιώντας την ακόμη περισσότερο εκμεταλλευόμενοι το λεξιλογικό πεδίο.

Βελτίωση της σαφήνειας με το λεξιλογικό πεδίο

Η έννοια πίσω από το λεξικό πεδίο είναι ότι όταν μια μεταβλητή συνδέεται με ένα περιβάλλον, άλλες διαδικασίες (συναρτήσεις) που ορίζονται σε αυτό το περιβάλλον έχουν πρόσβαση στην τιμή αυτής της μεταβλητής. Αυτό σημαίνει ότι στη συνάρτηση sqrt (), η παράμετρος x είναι συνδεδεμένη με αυτήν τη συνάρτηση, πράγμα που σημαίνει ότι οποιαδήποτε άλλη συνάρτηση που ορίζεται στο σώμα του sqrt () μπορεί να έχει πρόσβαση στο x.

Γνωρίζοντας αυτό, μπορούμε να απλοποιήσουμε ακόμη περισσότερο τον ορισμό του sqrt () αφαιρώντας όλες τις αναφορές στους ορισμούς συνάρτησης x, καθώς το x είναι τώρα μια δωρεάν μεταβλητή και προσβάσιμη από όλους. Εδώ είναι ο νέος ορισμός του sqrt ():

function sqrt(x) { function isGoodEnough(guess) { return (Math.abs(square(guess) - x)) > 0.001; } function improve(guess) { return average(guess, (x / guess)); } function sqrt_iter(guess) { if (isGoodEnough(guess)) { return guess; } else { return sqrt_iter(improve(guess)); } } return sqrt_iter(1.0);}

Οι μόνες αναφορές στην παράμετρο x είναι σε υπολογισμούς όπου απαιτείται η τιμή x. Ας φορτώσουμε αυτόν τον νέο ορισμό στο κέλυφος και να το δοκιμάσουμε:

> .load sqrt_iter.js> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> sqrt(123+37)12.649110680047308> sqrt(144)12.000000012408687

Η λεξική δομή scoping και block είναι σημαντικά χαρακτηριστικά του JavaScript και μας επιτρέπουν να κατασκευάσουμε προγράμματα που είναι πιο κατανοητά και διαχειρίζονται. Αυτό είναι ιδιαίτερα σημαντικό όταν αρχίζουμε να κατασκευάζουμε μεγαλύτερα, πιο περίπλοκα προγράμματα.