de Michael McMillan

O introducere ușoară în domeniul de aplicare Lexical în JavaScript

O introducere usoara in domeniul de aplicare Lexical in JavaScript
Fotografie de Templul Cerulean pe Unsplash

Scopul lexical este un subiect care îi sperie pe mulți programatori. Una dintre cele mai bune explicații ale scopului lexical poate fi găsită în cartea lui Kyle Simpson Nu-l cunoști pe JS: Domeniu de aplicare și închideri. Cu toate acestea, chiar și explicația sa lipsește, deoarece nu folosește un exemplu real.

Unul dintre cele mai bune exemple reale despre modul în care funcționează sfera lexicală și de ce este important, poate fi găsit în celebrul manual „Structura și interpretarea programelor de calculator” (SICP) de Harold Abelson și Gerald Jay Sussman. Iată un link către o versiune PDF a cărții: SICP.

SICP folosește Scheme, un dialect al Lisp, și este considerat unul dintre cele mai bune texte introductive de informatică scrise vreodată. În acest articol, aș dori să revizuiesc exemplul lor de scop lexical folosind JavaScript ca limbaj de programare.

Exemplul nostru

Exemplul folosit de Abelson și Sussman este calculul rădăcinilor pătrate folosind metoda lui Newton. Metoda lui Newton funcționează determinând aproximări succesive pentru rădăcina pătrată a unui număr până când aproximarea se încadrează într-o limită de toleranță pentru a fi acceptabilă. Să trecem la un exemplu, așa cum fac Abelson și Sussman în SICP.

Exemplul pe care îl folosesc este găsirea rădăcinii pătrate de 2. Începeți prin a face o presupunere la rădăcina pătrată a lui 2, să spuneți 1. Îmbunătățiți această presupunere împărțind numărul inițial la ghicirea și apoi calculând media respectivului coeficient și a ghicirii curente la vino cu următoarea ghicire. Te oprești când atingi un nivel acceptabil de aproximare. Abelson și Sussman folosesc valoarea 0,001. Iată o parcurgere a primilor pași în calcul:

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

Și așa mai departe până când presupunerea intră în limita noastră de aproximare, care pentru acest algoritm este 0,001.

O funcție JavaScript pentru metoda Newton

După această demonstrație a metodei, autorii descriu o procedură generală pentru rezolvarea acestei probleme în Scheme. În loc să vă arăt codul schemei, îl voi scrie în JavaScript:

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

În continuare, trebuie să concretizăm câteva alte funcții, inclusiv isGoodEnough () și îmbunătățire (), împreună cu alte funcții de ajutor. Vom începe cu îmbunătăți (). Iată definiția:

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

Această funcție utilizează o funcție de ajutor medie (). Iată această definiție:

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

Acum suntem gata să definim funcția isGoodEnough (). Această funcție servește pentru a determina când presupunerea noastră este suficient de apropiată de toleranța noastră de aproximare (0,001). Iată definiția lui isGoodEnough ():

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

Această funcție utilizează o funcție pătrat (), care este ușor de definit:

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

Acum nu ne trebuie decât o funcție pentru a începe lucrurile:

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

Această funcție folosește 1.0 ca presupunere de pornire, ceea ce este de obicei foarte bine.

Acum suntem gata să ne testăm funcțiile pentru a vedea dacă funcționează. Le încărcăm într-un shell JS și apoi calculăm câteva rădăcini pătrate:

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

Funcțiile par să funcționeze bine. Cu toate acestea, există o idee mai bună care se ascunde aici. Aceste funcții sunt scrise în mod independent, chiar dacă sunt menite să funcționeze împreună împreună. Probabil că nu vom folosi funcția isGoodEnough () cu niciun alt set de funcții sau pe cont propriu. De asemenea, singura funcție care contează pentru utilizator este funcția sqrt (), deoarece aceasta este cea care este chemată pentru a găsi o rădăcină pătrată.

Blocarea scopurilor ascunde funcțiile de ajutor

Soluția de aici este de a utiliza blocarea scopurilor pentru a defini toate funcțiile de asistență necesare în cadrul blocului funcției sqrt (). Vom elimina pătratul () și media () din definiție, deoarece acele funcții ar putea fi utile în alte definiții ale funcției și nu sunt la fel de limitate de a fi utilizate într-un algoritm care implementează metoda Newton. Iată definiția funcției sqrt () acum cu celelalte funcții de ajutor definite în sfera 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);}

Acum putem încărca acest program în shell-ul nostru și putem calcula câteva rădăcini pătrate:

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

Observați că nu puteți apela niciuna dintre funcțiile de ajutor din afara funcției 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

Întrucât definițiile acestor funcții (improve () și isGoodEnough ()) au fost mutate în sfera sqrt (), ele nu pot fi accesate la un nivel superior. Desigur, puteți muta oricare dintre definițiile funcției de ajutor în afara funcției sqrt () pentru a avea acces la acestea la nivel global, așa cum am făcut cu average () și square ().

Am îmbunătățit considerabil implementarea metodei lui Newton, dar încă mai putem face un lucru pentru a ne îmbunătăți funcția sqrt () simplificându-l și mai mult, profitând de sfera lexicală.

Îmbunătățirea clarității cu domeniul lexical

Conceptul din spatele scopului lexical este că atunci când o variabilă este legată de un mediu, alte proceduri (funcții) care sunt definite în acel mediu au acces la valoarea variabilei respective. Aceasta înseamnă că în funcția sqrt (), parametrul x este legat de acea funcție, ceea ce înseamnă că orice altă funcție definită în corpul sqrt () poate accesa x.

Știind acest lucru, putem simplifica definiția sqrt () și mai mult prin eliminarea tuturor referințelor la x din definițiile funcției, deoarece x este acum o variabilă gratuită și accesibilă tuturor. Iată noua noastră definiție a 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);}

Singurele referințe la parametrul x sunt în calculele în care este necesară valoarea lui x. Să încărcăm această nouă definiție în shell și să o testăm:

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

Scopul lexical și structura blocurilor sunt caracteristici importante ale JavaScript-ului și ne permit să construim programe mai ușor de înțeles și de gestionat. Acest lucru este important mai ales atunci când începem să construim programe mai mari și mai complexe.

1612143366 386 O introducere usoara in domeniul de aplicare Lexical in JavaScript