Једноставан увод у лексички опсег у ЈаваСцрипт-у

Лексички опсег је тема која плаши многе програмере. Једно од најбољих објашњења лексичког опсега може се наћи у књизи Кајла Симпсона Иоу Дон'т Кнов ЈС: Сцопе анд Цлосурес. Међутим, чак и његово објашњење недостаје јер не користи прави пример.

Један од најбољих стварних примера како лексичко опсег функционише и зашто је важан, може се наћи у чувеном уџбенику „Структура и тумачење рачунарских програма“ (СИЦП) Харолда Абелсона и Гералда Јаи Суссмана. Ево везе до ПДФ верзије књиге: СИЦП.

СИЦП користи шему, дијалект Лисп, и сматра се једним од најбољих уводних текстова из рачунарства икада написаних. У овом чланку бих желео да поново погледам њихов пример лексичког опсега помоћу ЈаваСцрипт-а као програмског језика.

Наш пример

Пример који су Абелсон и Суссман користили је израчунавање квадратних корена помоћу Њутнове методе. Њутнова метода делује одређивањем узастопних апроксимација квадратног корена броја све док апроксимација не уђе у границу толеранције да би била прихватљива. Порадимо на примеру, као што то раде Абелсон и Суссман у СИЦП-у.

Пример који користе је проналажење квадратног корена из 2. Почињете са погађањем на квадратном корену из 2, рецимо 1. Побољшавате ову претпоставку тако што делите оригинални број са претпоставком, а затим просечите тај количник и тренутну претпоставку на смислите следећу претпоставку. Заустављате се када достигнете прихватљив ниво апроксимације. Абелсон и Суссман користе вредност 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.

ЈаваСцрипт функција за Њутнов метод

Након ове демонстрације методе, аутори описују општи поступак за решавање овог проблема у Шеми. Уместо да вам покажем код шеме, исписаћу га у ЈаваСцрипт-у:

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

Даље, морамо да разрадимо неколико других функција, укључујући исГоодЕноугх () и побољшати (), заједно са неким другим помоћним функцијама. Почећемо са побољшањем (). Ево дефиниције:

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

Ова функција користи помоћну функцију просјек (). Ево те дефиниције:

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

Сада смо спремни да дефинишемо функцију исГоодЕноугх (). Ова функција служи за одређивање када је наша претпоставка довољно близу наше апроксимационе толеранције (0,001). Ево дефиниције исГоодЕноугх ():

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 као почетну претпоставку, што је обично сасвим у реду.

Сада смо спремни да тестирамо своје функције како бисмо видели да ли раде. Учитавамо их у ЈС љуску, а затим израчунавамо неколико квадратних корена:

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

Изгледа да функције добро функционишу. Међутим, овде постоји боља идеја. Све ове функције су написане независно, иако им је предвиђено да раде заједно. Вероватно нећемо користити функцију исГоодЕноугх () са било којим другим скупом функција или самостално. Такође, кориснику је важна само функција скрт (), с обзиром да је она позвана да пронађе квадратни корен.

Опсег блокова сакрива помоћне функције

Овде је решење коришћење опсега блокова за дефинисање свих потребних помоћних функција унутар блока скрт () функције. Уклонићемо квадрат () и просек () из дефиниције, јер би те функције могле бити корисне у другим дефиницијама функција и нису толико ограничене за употребу у алгоритму који имплементира Њутнов метод. Ево дефиниције скрт () функције сада са осталим помоћним функцијама дефинисаним у опсегу скрт ():

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(9)3.00009155413138> sqrt(2)1.4142156862745097> improve(1,2)ReferenceError: improve is not defined> isGoodEnough(1.414, 2)ReferenceError: isGoodEnough is not defined

Будући да су дефиниције ових функција (побољшати () и исГоодЕноугх ()) премештене унутар опсега скрт (), не може им се приступити на вишем нивоу. Наравно, било коју дефиницију помоћне функције можете преместити изван скрт () функције да бисте им приступили глобално, као што смо то учинили са авераге () и скуаре ().

Значајно смо побољшали нашу примену Њутнове методе, али још увек можемо учинити још једну ствар да бисмо побољшали нашу функцију скрт () тако што ћемо је још више поједноставити користећи предност лексичког опсега.

Побољшање јасноће помоћу лексичког опсега

Концепт лексичког опсега је да када је променљива везана за окружење, други поступци (функције) који су дефинисани у том окружењу имају приступ вредности те променљиве. То значи да је у скрт () функцији параметар к везан за ту функцију, што значи да било која друга функција дефинисана у телу скрт () може приступити к.

Знајући ово, можемо још више поједноставити дефиницију скрт () уклањањем свих референци на к у дефиницијама функција, јер је к сада бесплатна променљива и свима њима је доступан. Ево наше нове дефиниције скрт ():

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);}

Једине референце на параметар к су у прорачунима где је потребна вредност к. Учитајмо ову нову дефиницију у љуску и тестирајте је:

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

Лексички опсег и структура блокова су важне карактеристике ЈаваСцрипт-а и омогућавају нам да конструишемо програме који су лакши за разумевање и управљање. Ово је посебно важно када почнемо да градимо веће, сложеније програме.