Област видљивости

С Википедије, слободне енциклопедије

У програмирању, област видљивости (eng. scope ) је део програмског кода у коме је везивање имена (eng. name binding ) валидно, при чему се име може користити за упућивање на ентитет. Таква област назива се scope block . У осталим деловима кода име се може односити на други ентитет (може бити другачије везано) или може бити неповезано.

Област везивања је такође позната као видљивост ентитета, посебно у старијој литератури - из перспективе наведеног ентитета, а не референтног назива.

Oбласт видљивости променљиве је део програма који је или може бити област скупа везивања - тешко је прецизно дефинисати, али у пракси у великој мери одговара блоку, функцији или датотеци, зависно од програмског језика и типа ентитета. Израз "scope" користи се и за упућивање на скуп свих видљивих ентитета или имена која важе у делу програма или у одређеној датој тачки програма, што се прецизније назива context или окружење.

У пракси се за већину програмских језика „део програма“ упућује на „део изворног кода (подручје текста)“ и познат је као лексичка област видљивости (lexical scope). Међутим, у неким програмским језицима се „део програма“ односи на „део времена извођења (време извршавања кода)“ и познат је као динамичка област видљивости(dynamic scope).

У већини случајева name resolution заснована на лексичкој области видљивости релативно је једноставна за употребу и за примену, јер се током употребе може прочитати уназад у изворном коду да би се утврдило на који се ентитет једно име односи, а у имплементацији се може одржавати листа имена и контекста приликом компајлирања или интерпретације програма. Потешкоће настају при маскирању имена и хостовању, док знатно суптилније настају код нелокалних променљивих, посебно у затворењима.

Дефиниција[уреди | уреди извор]

Лексичка област видљивости имена (идентификатора) је „део изворног кода на који се односи везивање имена са ентитетом“ - и дефиниција је практично непромењена у односу на исту из 1960. године по спецификацији ALGOL 60. Следе спецификације језика ALGOL 60.

ALGOL 60 (1960)

Разликују се следеће врсте величина: једноставне променљиве, низови, ознаке, промене и процедуре. Област видљивости величине је скуп исказа и израза у којима је декларација идентификатора повезаног с том величином валидна.

C (2007)[1]

Идентификатор може означавати објекат, функцију, члана структуре, везе или набрајања, typedef name, име ознаке, име макро-a или макро параметар. Исти идентификатор може означавати различите ентитете у различитим тачкама програма. За сваки различити ентитет који означи идентификатор, идентификатор је видљив (тј. може се користити) само у подручју текста програма који се зове његова област видљивости.

Go (2013)[2]

Декларација везује non-blank идентификатор за константу, тип, променљиву, функцију или пакет. Област видљивости декларисаног идентификатора је опсег изворног текста у којем идентификатор означава наведену константу, тип, променљиву, функцију или пакет.

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

Лексички опсег насупрот динамичком опсегу[уреди | уреди извор]

Основна разлика у опсегу обухвата значење „дела програма“. У језицима с лексичким опсегом (који се називају и статички опсег), резолуција имена зависи од локације у изворном коду и лексичког контекста, која је дефинисана местом дефинисане именоване променљиве или функције. Супротно томе, у језицима са динамичким опсегом резолуција имена зависи од стања програма у коме се појављује име које је одређено контекстом извршавања или контекстом позива. У пракси, са лексичким опсегом дефиниција променљиве решава се претрагом њеног блока који садржи или функцију, затим ако то не успе у претраживању спољног блока и слично, док се са динамичким опсегом тражи функција позива, тада функција која је позвала тај позив функцију, и тако даље, напредовање на низу позива.[3] Наравно, у оба правила прво тражимо локалну дефиницију променљиве.

Већина савремених језика користи лексичко одређивање за променљиве и функције, мада се динамички опсег користи у неким језицима, нарочито у неким дијалектима Лисп-а, неким „скриптним“ језицима и неким језицима шаблона. Перл 5 нуди и лексички и динамички опсег. Чак и у лексички обухваћеним језицима, простор за затворење може бити збуњујући, јер они зависе од лексичког контекста где је затворење дефинисано, а не где се назива.

Лексичка резолуција се може одредити у току превођења, а позната је и као рано везивање, док се динамичка резолуција у целини може одредити само током извођења, па је стога позната и као касно везивање.

Сродни концепти[уреди | уреди извор]

У објектно оријентисаном програмирању динамичко обавештење одабира објектну методу за време извођења, иако се стварно везивање имена врши у време превођења или време извођења у зависности од  језика. De facto динамички опсег је уобичајен у макро језицима, који не омогућавају директно разрешавање имена, већ се уместо тога проширују.

Неки програмски оквири попут AngularJS користе израз „домет“ да би значили нешто сасвим друго од начина на који се користи у овом чланку. У тим је оквирима само предмет програмског језика који користе (ЈаваСкрипт у случају АнгуларЈС) који се на одређени начин користи оквиром за опонашање динамичког опсега на језику који за своје променљиве користи лексички опсег. Ти опсези AngularJS могу сами бити у домету или изван његовог домета(користећи уобичајено значење термина) у било ком делу програма, следећи уобичајена правила променљивог опсега језика као и било који други објекат и користећи сопствено наслеђивање и правила о искључивању. У контексту AngularJS, понекад се користи термин „$опсег“ (са знаком долара) да се избегне забуна, али употреба знака долара у променљивим именима често обесхрабрујуће водиче за стил.[4]

Употреба [уреди | уреди извор]

Опсег је важна компонента name resolution,  која је за узврат основна за језичку семантику. Резолуција имена (укључујући опсег) варира између програмских језика и унутар програмског језика, овисно о врсти ентитета; правила за обим се називају правила о опсегу или правила за обим. Заједно са именима простора, правила за опсег су кључна за модуларно програмирање, тако да промена у једном делу програма не прекида неповезани део.

Преглед[уреди | уреди извор]

Када се говори о домету, постоје три основна концепта: обим, обим и контекст. Конкретно „опсег“ и „контекст“ често се мешају: обим је особина идентификатора и фиксан је, док је контекст особина програма, који се разликује зависно од положаја. Тачније, контекст је особина положаја у програму, било позиције у изворном коду (лексички контекст) или тачке током времена извођења (контекст извршења, контекст извођења или контекст позива). Контекст извршења састоји се од лексичког контекста (у тренутној извршној тачки) плус додатног стања извршења, као што је стог позива.  Дакле, када се тачка извршења програма налази у опсегу имена променљиве, "променљива (име) је у контекст "(што значи" у контексту у овој тачки "), а када извршна тачка" напушта опсег променљиве (име) ", као што је повратак из функције," променљива (име) излази из контекста " . Уско речено, током извођења програм улази и излази из различитих опсега, а на месту извршења идентификатори су "у контексту" или "нису у контексту", дакле идентификатори "долазе у контекст" или "излазе из контекста" како програм улази или излази из опсега - међутим, у пракси је употреба много слабија. Опсег је концепт нивоа изворног кода и својство идентификатора, посебно променљивих или функција, функција - идентификатори у изворном коду су референце на ентитете у програму - и део је понашања преводиоца или преводиоца језика.

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

За ентитете као што су променљиве, опсег је подскуп животног века (познат и као обим) - име се може односити само на променљиву која постоји (вероватно са недефинисану вредност), али променљиве које постоје нису нужно видљиве: променљива може постојати, али бити неприступачна (вредност се чува, али се не односи у датом контексту) или је доступна, али не преко датог имена; у том случају је ван контекста (програм је "ван опсега имена"). У другим случајевима „животни век“ је ирелевантан - налепница (именована позиција у изворном коду) има век трајања идентичан програму (за статички компајлиране језике), али може бити у или изван контекста у одређеној тачки програма, и слично за статичке променљиве - статичка глобална променљива је у контексту за цео програм, док је статична локална променљива само у контексту неке функције или другог локалног контекста, али обе имају век трајања читавог програма.

Нивои опсега[уреди | уреди извор]

Опсег може варирати од само једног израза до читавог програма, са много могућих градација између. Најједноставније правило за оцењивање је глобални опсег - сви субјекти су видљиви током целог програма. Најосновније модуларно правило за опсег обухвата опсег на два нивоа, са глобалним опсегом било где у програму и локалним опсегом унутар функције. Софистицираније модуларно програмирање омогућава засебан домет модула, где су имена видљива унутар модула (приватна за модул), али нису видљива изван њега. У оквиру неке функције, неки језици, као што је C, дозвољавају опсег блока да ограничи опсег на подскуп функције; други, посебно функционални језици, омогућавају опсег израза да ограничи опсег на један израз. Остали обим обухвата опсег датотека (посебно на Ц) који се понаша слично као опсег модула и блокира опсег изван функција (посебно на Перлу).

Суптилно питање је то када се опсег започиње и када се заврши. У неким језицима, као што је Ц, опсег имена започиње његовом декларацијом, тако да различита имена декларисана у одређеном блоку могу имати различите домете. Ово захтева декларисање функција пре употребе, мада их не мора нужно и дефинисати, и захтева преношење декларације у неким случајевима, посебно за међусобну рекурзију. У осталим језицима, као што су JavaScript или Python опсег имена почиње на почетку релевантног блока (попут покретања функције), без обзира на то где је дефинисан, и сва имена унутар одређеног блока имају исти опсег; у JavaScript је то познато као дизање променљивих. Међутим, када се назив веже за неку вредност варира, а понашања у контекстним именима која имају недефинисану вредност се разликују.

Опсег израза[уреди | уреди извор]

Многи језици, нарочито функционални језици, нуде функцију која се зове let-expressions, која омогућава да опсег декларације буде самосталан израз. То је погодно ако је, на пример, потребна једна средња вредност за рачунање. На пример, у стандардном ML  if f() returns 12, then let val x = f() in x * x end је израз који има вредност 144, користећи привремену променљиву x како би се избегло двоструко позивање f(). Неки језици са опсегом блока приближавају ову функцију нудећи синтаксу за уметање блока у израз; на пример, горе поменути стандардни МЛ израз може се написати у Перлу као do { my $x = f(); $x * $x },или у GNU C као ({ int x = f(); x * x; }).

У Python-u помоћне променљиве у изразима генератора имају опсег израза.

У C-u, имена променљивих у прототипу функције имају опсег израза, у овом контексту познат као опсег протокола функције.

Scope block[уреди | уреди извор]

Многи, али не сви, програмски језици омогућавају да се опсег ограничи на блок, који је познат и као опсег блока. Ово је почело АЛГОЛ-ом 60, где „[е] very declaration ... важи само за тај блок.“[5], а данас је посебно повезана са језицима у породицама и традицијама Pascal и C. Најчешће се овај блок налази унутар функције, чиме се домет ограничава на део функције, али у неким случајевима, као што је Перл, блок можда није у функцији.

unsigned int sum_of_squares(const unsigned int N) {

  unsigned int ret = 0;

  for (unsigned int n = 1; n <= N; n++) {

   const unsigned int n_squared = n * n;

   ret += n_squared;

  }

  return ret;

}


Репрезентативни пример употребе блок-опсега је C код који је овде приказан, где се две променљиве доводе у петљу: променљива петље n, која се иницијализује једном и повећава на свакој итерацији петље, и помоћна променљива n_squared, која се иницијализује при свакој итерацији. Сврха је избећи додавање променљивих у опсег функција које су релевантне само за одређени блок - на пример, то спречава грешке код којих је генеричка променљива петље случајно већ постављена на другу вредност. У овом примеру израз н * н генерално не би био додељен помоћној променљивој, а тело петље би једноставно било записано ret + = n * n, али у сложенијим примерима су корисне помоћне променљиве.

Блокови се првенствено користе за контролисање протока, као што су if, while, I for петље, а у тим случајевима опсег блока значи да опсег променљиве зависи од структуре протока извршења функције. Међутим, језици са опсегом блока обично омогућавају и употребу „голих“ блокова, чија је једина сврха омогућавање fine-grained контроле променљивог опсега. На пример, помоћна променљива се може дефинисати у блоку, затим користити (рецимо, додати променљивој са опсегом функције) и одбацити када се блок заврши, или неко време петља може бити затворена у блоку који иницијализује променљиве које се користе унутар петље то би требало да буде иницијализирано само једном.

Опсег блока може се користити за сенчење. У овом примеру, унутар блока би се помоћна променљива могла назвати и n, сенчећи назив параметра, али ово се сматра лошим стилом због могућности грешака. Надаље, неки потомци C-а, као што су Јава и С# упркос томе што имају подршку за блок опсег (при чему локална променљива може бити направљена да изађе из домета пре краја функције), не дозвољавају једној локалној променљивој да сакрије другу . У таквим језицима покушај декларације другог n резултирао би синтаксном грешком, а једна од n променљивих би морала бити преименована.

Ако се блок користи за постављање вредности променљиве, опсег блока захтева да се променљива декларише изван блока. Ово усложњава употребу условних изјава са једним задатком. На пример, у Python-u, који не користи опсег блока, може иницијализовати променљиву као такву:

if c:
    a = 'foo'
else:
    a = ''

где је a доступно након if услова. У Перлу који има опсег блока, ово уместо тога захтева проглашавање променљиве пре блока:

my $a;
if (c) {
  $a = 'foo';
} else {
  $a = '';
}

У Python-u то би било:

a = ''
if c:
    a = 'foo'

Док би ово у Perl-u било:

my $a = '';
if (c) {
  $a = 'foo';
}

У случају додељивања једне променљиве, алтернатива је употреба тернарног оператора да се избегне блок, али то није могуће за додељивања више променљивих и тешко је читати сложеном логиком.

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

Опсег функције[уреди | уреди извор]

Већина програмских језика нуди начин креирања локалне променљиве у функцији или потпрограму: променљива чији се опсег завршава (који излази из контекста) када се функција врати. У већини случајева, животни век променљиве је трајање позива функције - то је аутоматска променљива, створена када се функција покрене (или се променљива декларише), уништи када се функција врати - док је опсег променљиве унутар функција, мада значење „унутар“ зависи од тога да ли је опсег лексички или динамички.

Важно је да у лексичком опсегу променљива са опсегом функције има опсег само унутар лексичког контекста функције: она излази из контекста када се друга функција позива у функцији, и враћа се у контекст када се функција врати - позване функције немају приступ локалним променљивим позивајућих функција, а локалне променљиве су само у контексту унутар тела функције у којој су декларисане. Супротно томе, у динамичком опсегу опсег се наставља на контекст извођења функције: локалне променљиве остају у контексту када се позива друга функција, само се крећу ван контекста када се функција дефинисања заврши, и зато су локалне променљиве у контексту функције у којој су дефинисане и све се називају функцијама. У језицима са лексичким опсегом и угнежденим функцијама, локалне променљиве су у контексту угнеждених функција јер су унутар истог лексичког контекста, али не и за остале функције које нису лексички угнеждене. Локална променљива ограђујуће функције позната је као не-локална променљива угнеждене функције. Опсег функција је такође применљив на анонимне функције.

На пример, у исечку Python кода доле, дефинисане су две функције: square  и sum_of_squares. square  . Square рачуна квадрат броја; sum_of_squares израчунава збир свих квадрата.(израчунава На пример, квадрат (4) је 42 = 16, а збир квадрата је (4) је 02 + 12 + 22 + 32 + 42 = 30).

Свака од ових функција има променљиву са именом n која представља аргумент функције. Ове две n променљиве су потпуно одвојене и неповезане, упркос томе што имају исти назив, јер су лексички обухваћене локалне променљиве са опсегом функција: опсег сваке променљиве је њен лично, лексички одвојена функција и на тај начин се не преклапају. Због тога, sum_of_squares  може позвати square без да се његов n мења. Слично томе, sum_of_squares има променљиве назване total и i; ове променљиве, због свог ограниченог опсега, неће ометати ниједну променљиву под називом total или i која би могла припадати било којој другој функцији. Другим речима, не постоји ризик од судара имена између ових идентификатора и било којих неповезаних идентификатора, чак и ако су идентични.

def square(n):
    return n * n

def sum_of_squares(n):
    total = 0 
    i = 0
    while i <= n:
        total += square(i)
        i += 1
    return total

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

Опсег датотеке[уреди | уреди извор]

Правило опсега понајвише одређено за C(и C++) је опсег датотеке, при чему је опсег променљивих и функција декларисаних на врху датотеке за целу датотеку - или боље за C, од декларације до краја изворне датотеке, тачније јединице за превођење (унутрашње повезивање). Ово се може посматрати као облик опсега модула, где су модули идентификовани са датотекама, а на модернијим језицима је замењен експлицитним опсегом модула. Због присуства израза који укључују, који додају променљиве и функције унутрашњем контексту и могу се сами позивати даље укључују изјаве, што може бити тешко одредити шта је у контексту у телу датотеке.

У горњем исечку C кода, назив функције  sum_of_squares има опсег датотеке.

Опсег модула[уреди | уреди извор]

У модуларном програмирању опсег имена може бити читав модул, али може бити структуриран кроз различите датотеке. У овој парадигми модули су основна јединица сложеног програма, јер омогућавају сакривање и излагање информација ограниченом интерфејсу.

У неким објектно-оријентисаним програмским језицима којима недостаје директна подршка за модуле, као што је C++, слична структура је уместо ње обезбеђена хијерархијом класа, где су класе основна јединица програма, а класа може имати приватне методе. То се правилно разуме у контексту динамичког слања, уместо резолуције и опсега имена, мада често играју аналогне улоге. У неким случајевима су доступна оба објекта, као што је на пример Python који има и модуле и класе, а организација кода (као функција на нивоу модула или конвенционално приватна метода) је избор програмера.

Глобални опсег[уреди | уреди извор]

Декларација има глобални опсег ако делује на читав програм. Имена променљивих са глобалним опсегом - назване глобалне променљиве - често се сматрају лошом праксом, бар у неким језицима, због могућности сударања имена и ненамерног маскирања, заједно са слабом модуларношћу и опсегом функција или опсегом блока, сматрају се пожељнијим. Међутим, глобални опсег се обично користи за разне друге врсте идентификатора, попут назива функција и имена класа и других врста података. У овим случајевима механизми попут простора с именима се користе како би се избегли судари.

Лексички опсег насупрот динамичком оспегу[уреди | уреди извор]

Употреба локалних променљивих - имена променљивих са ограниченим опсегом, која постоје само у оквиру одређене функције - помаже да се избегне ризик од судара имена две идентично именоване променљиве. Међутим, постоје два врло различита приступа одговору на ово питање: Шта значи бити у оквиру функције?

У лексичком опсегу, ако је опсег имена променљиве одређена функција, онда је његов опсег програмски текст дефиниције функције: унутар тог текста, име променљиве постоји и везано је  за вредност променљиве, али изван тог текста, име променљиве не постоји. Супротно томе, у динамичком опсегу, ако је опсег имена променљиве одређена функција, онда је његов опсег временски период током кога се функција извршава: док се функција покреће, име променљиве постоји и јесте везано за своју вредност, али након што се функција врати, назив променљиве не постоји. То значи да ако функција f позива посебно дефинисану функцију g онда под лексичким опсегом функција g нема приступ локалним променљивама f док под динамичким опсегом функција g има приступ локалним променљивим f .

$ # bash language
$ x=1
$ function g () { echo $x ; x=2 ; }
$ function f () { local x=3 ; g ; }
$ f # does this print 1, or 3?
3
$ echo $x # does this print 1, or 2?
1

Разматрамо,на пример програм изнад.  Прва линија x=1 креира глобалну променљиву x и иницијализује је на 1. Друга линија function g () { echo $x ; x=2 ; } дефинише функцију g која штампа ("echoes") тренутну вредност x, а затим поставља x  на 2. Трећа линија, function f () { local x=3 ; g ; }  дефинише функцију f која креира локалну променљиву x, и иницијализује је на 3. Четврта линија, f позива f. Пета линија, echo $x штампа тренутну вредност x.

Дакле, шта тачно штампа овај програм? То зависи од правила за опсег. Ако је језик овог програма онај који користи лексички опсег, тада g штампа и модификује глобалну променљиву x (јер је g дефинисан изван f), тако да програм штампа 1, а затим 2. За разлику од тога, ако овај језик користи динамичкi опсег, онда g исписује и мења локалну променљиву x функције f(јер се g позива из f), тако да програм штампа 3, а затим 1.

Лексички опсег[уреди | уреди извор]

Лексички опсег се односи на локално лексичко окружење.Ово је својство програмског текста, а приликом имплементације извршавање се одиграва независно од скупа позива.Лексички опсег се још назива и „статички опсег“ . Код језика који су засновани на  ALGOL-u као што су  Pascal, Modula-2 и Ada лексички опсег је стандардан,али поред њих заступљен је и у модерним функционалним језицима као што су ML and Haskell . Статички обухваћени језици међу којима је и С се разликују по нивоу ограничености. Статички опсег дозвољава програмеру да размишља о објектима референци, као што су параметри, променљиве, константе, врсте, функције итд, као једноставним заменама имена.То представља велико олакшање приликом прављења модуларног кода.

Нпр. лексички обухваћен је и Pascal, размислите о коду који је написан у програмском језику Pascal

program A;
var I:integer;
    K:char;

    procedure B;
    var K:real;
        L:integer;

        procedure C;
        var M:real;
        begin
         (*scope A+B+C*)
        end;

     (*scope A+B*)
    end;

 (*scope A*)
end.

Променљива I је видљива у свим тачкама, јер никада није сакривена од стране друге истоимене променљиве.За разлику од I, променљива К је видњива само у главном програму, јер је скривена од стране стварне променљиве К која је видљива само у процедурама B и C. Попут променњиве К, променљива L, али не скрива остале променљиве. Променљива М је видљива само у процедури C, из тог разлога није је могуће позвати ни из главног програма,  а ни из поступка B. Такође, процедура С је видљива само у процедури B и због тога је не можете позвати из главног програма.

Место у програму где се помиње "C" тада одређује који од два поступка под називом C се користи тачно.

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

Историја[уреди | уреди извор]

Лексички опсег коришћен је за императивни језик ALGOL60 и од тада је почео да се користи у већини осталих императивних језика.

Језици попут Pascal и C одувек су имали лексички опсег, док је Perl језик са динамичким опсегом.

Оригинални Lisp преводилац (1960) користио је динамички опсег. Дубоко везивање, које приближава статички (лексички) опсег, уведено је у Lisp 1.5 (преко Фунарг уређаја који је развио Steve Russell).

Сви рани Лиспови користили су динамичи опсег, барем на основу тумача. Године 1982. Guy L. Steele Jr. и Common LISP Group објављују An overview of Common LISP,[6] кратак преглед историје и дивергентних имплементација Лиспа до тог тренутка и преглед карактеристика које заједнички Лисп примена треба да има. На страници 102 читамо:

Већина ЛИСП имплементација интерно је недоследна у томе што по тумачењу преводилац и компајлер могу доделити различите семантике за исправљање програма; ово произилази пре свега из чињенице да тумач претпоставља да су све променљиве динамички обухваћене, док преводилац претпоставља да су све променљиве локалне, осим ако нису приморани да претпоставе другачије. То је учињено ради практичности и ефикасности, али може довести до веома суптилних грешака. Дефиниција Common LISP-а избегава такве аномалије изричито захтевајући од преводиоца и компајлера да намећу идентичну семантику исправним програмима.

Имплементација Common LISP-а је због тога требала имати лексички опсег. Поново из An overview of Common LISP:

Поред тога, Common LISP нуди следеће погодности. Потпуно лексички обухваћене променљиве. Такозвани „проблем ФУНАРГ“ [7]потпуно је решен, и у случају горе и доле.

Исте године у којој је објављен An overview of Common LISP  (1982), објављени су почетни дизајни (такође Гуи Л. Стееле Јр.) састављеног, лексички обухваћеног Лиспа, названог Scheme. У то време  лексички опсег у Лиспу страхује да није ефикасан за спровођење.

Израз "лексички опсег" датира најмање од 1967.[8] [9]године, док термин "лексички опсег" датира бар од 1970. године, где је коришћен у Project MAC-u за опис правила опсега  Лисповог дијалекта МДЛ.[10]

Динамички опсег[уреди | уреди извор]

Са динамичким опсегом, глобални идентификатор односи се на идентфикатор повезан са најновијим окружењем и неуобичајен је у савременим језицима. Технички гледано, то значи да сваки идентификатор има глобални низ везивања. Увођењем локалне променљиве са називом x гура везивањеу глобални стек, који искаче када контролни ток напусти опсег. Процена x у било ком контексту увек даје горњу обавезу. Имајте на уму да се то не може извршити у време компајлирања, јер везивање сета постоји само у време извођења, због чега се ова врста везивања назива динамичким опсегом.

Генерално, одређени блокови су дефинисани тако да стварају везивања чији је животни век време извршења блока; ово додаје неке карактеристике статичког опсега у процес динамичког опсега. Међутим, будући да се део кода може позвати са много различитих локација и ситуација, на почетку може бити тешко одредити која ће се везивања примењивати када се користи променљива. Ова уска интерпретација дељених података може пружити врло флексибилан систем за прилагођавање понашања функције у тренутном стању система. Међутим, ова корист се ослања на пажљиву документацију свих променљивих који се користе на овај начин, као и на пажљиво избегавање претпоставки о понашању променљиве и не пружа никакав механизам за откривање сметњи између различитих делова програма. Динамички опсег такоже поништава све вредности референтне транспарентности. Као такав, димачки опсег може бити опасан и мало савремених језика га користи. Неки језици, попут Perl иCommon Lisp омогућавају програмеру да одабере статички или динамички опсег приликом дефинисања или редефинисања променљиве. Примери језика који користе динамичко оцењивање укључују  Logo, Emacs Lisp, LaTeX и језике bash, dash, and PowerShell.

Динамички опсег је прилично лако имплементирати. Да би пронашао вредност идентификатора, програм би могао да пређе време извршавања стека, проверавајући да ли сваки запис активације има вредност за идентификатор. У пракси се то постиже ефикасније коришћењем асоцијативне листе, која представља стек парова назива/ вредности. Парови се гурају на овај стек кад год се декларације изграде и искачу кад год променљиве изађу из опсега.[11] Плитко везивање је алтернативна стратегија која је знатно бржа и која користи централну референтну табелу која свако име повезује са сопственим стеком значења. Овим се избегава линеарна претрага током извршавања да би се пронашао одређени назив, али треба водити рачуна о правилном одржавању ове табеле. Имајте на уму да обе ове стратегије претпостављају последњи-први-излаз (ЛИФО) да би наредио повезивање за било коју променљиву; у пракси су све везе тако наручене.

Још једноставнија имплементација је представљање динамичких променљивих са једноставним глобалним променљивама. Локално везивање се изводи тако што се оригинална вредност сачува на анонимној локацији на стеку који је програму невидљив. Када се тај опсег везивања прекине, изворна вредност се враћа са ове локације. У ствари, динамички опсег је настао на овај начин. Ране имплементације Lisp-а користиле су ову очигледну стратегију за имплементацију локалних променљивих, а пракса опстаје у неким дијалектима који су и даље у употреби, као што је GNU Emacs Lisp. Касније је у Lisp уведен лексички опсег. То је еквивалентно горњој плиткој шеми везивања, осим што је централна референтна табела једноставно глобално окружење, у којој је тренутно значење променљиве њена глобална вредност. Одржавање глобалних променљивих није сложено. На пример, симболни објект може имати наменски слот за своју глобалну вредност.

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

Макро експанзија[уреди | уреди извор]

У модерним језицима, макро експанзија у претпрограму је кључни пример de facto динамичког опсега. Сам језик макро наредбе трансформише изворни код, али пошто се проширење врши на месту, где се имена у проширеном тексту решавају, они се решавају на основу места на коме су проширени, као да се догађа динамички опсег.

Потпрограм у С, који се користи за макро проширење, има de facto динамички опсег, јер сам по себи не доноси резолуцију имена. На пример, макро:

#define ADD_A(x) x + a

ће се проширити за додавање а пренесеној променљивој, а овај идентификатор ће компајлер касније разрешити тек на основу места где је макро ADD_A  „позван“ у динамичком опсегу и не зависи од места где је макро дефинисан. Исправно, C препроцесор ради само лексичку анализу, проширујући макро наредбу током фазе токенизације. На пример, у следећем коду макро а у макро се разрешава на локалну променљиву на месту проширења:

#define ADD_A(x) x + a

void add_one(int *x) {
  const int a = 1;
  *x = ADD_A(*x);
}

void add_two(int *x) {
  const int a = 2;
  *x = ADD_A(*x);
}

Квалификовани идентификатори[уреди | уреди извор]

Као што смо видели, један од кључних разлога опсега је тај што помаже у спречавању судара имена, омогућавајући идентичним идентификаторима да се односе на различите ствари, уз ограничење да идентификатори морају имати засебне домете. Понекад је ово ограничење непријатно; када много различитих ствари мора бити доступно кроз програм, углавном им је потребан идентификатор глобалног опсега, па су потребне различите технике да се избегнe сударање имена.

Да би се ово решило, многи језици нуде механизме за организовање глобалних идентификатора. Детаљи ових механизама и коришћени појмови зависе од језика; али општа идеја је да групa идентификатора може и самa sebi  дати име - префикс – и. Нормално ће такви идентификатори имати, у одређеном смислу, два скупа опсега: опсег (обично глобални опсег) у којем је квалификовани идентификатор видљив и један или више ужих опсега у којима је неквалификовани идентификатор (без префикса) видљив такође. И обично се ове групе могу и саме организовати у групе; то јест, могу се угнездити.

Иако многи језици подржавају овај концепт, детаљи се увелико разликују. Неки језици имају механизме, као што су namespaces  у C++ and C# који служе готово искључиво да би се омогућило организовање глобалних идентификатора у групе. Други језици имају механизме, као што су пакети у Ади и структуре у Стандардном ML-u, који то комбинују са додатном сврхом омогућавања да неки идентификатори буду видљиви само осталим члановима њихове групе. И објектно оријентисани језици често допуштају да класе или singleton објекти испуњавају ову сврху (без обзира да ли они имају и механизам за који је то главна сврха). Језици често спопадају ове приступе; на пример, Perl пакети су у великој мери слични просторима имена C++ али опционо се дуплирају као класе за објектно оријентисано програмирање; и Јава организује своје променљиве и функције у класе, али их затим организује у пакете сличне Adi.

По језику[уреди | уреди извор]

С[уреди | уреди извор]

У С-у опсег је традиционално познат као повезивање или видљивост, посебно за променљиве. С је лексички обиман језик са глобалним опсегом (познат као спољна веза), опег модула или опсег датотеке (познат као унутрашња веза) и локални опсег (у оквиру функције); у оквиру функција опсег се може даље угнездити преко блок-опсега. Међутим, стандардни С не подржава угнездене функције.

Животни век и видљивост променљиве одређује се њеном класом чувања. Постоје три врсте животних векова у С-у: статички (извршење програма), аутоматски (извршавање блокова, додељено на хрпи) и ручни (додељен на хрпи). Само су статички и аутоматски подржани  за променљиве и којима компајлер рукује, док ручно додељена меморија мора бити ручно праћена кроз различите променљиве. У С-упостоје три нивоа видљивости: спољна веза (глобална), унутрашња веза (отприлике датотека) и опсег блока (који укључује функције). Унутрашња повезаност на С-у је видљивост на нивоу преводилачке јединице, наиме изворна датотека након што је С препроцесор обрађивао, посебно укључујући све релевантне обухвата.

С програми се компајлирају као засебне објектне датотеке, које се потом повезују у извршну датотеку или библиотеку путем повезивача. Тако се резолуција имена дели широм компајлера, који решава имена унутар преводитељске јединице и везник, који решава имена по преводилачким јединицама; види везу за даљу дискусију.

У С-у променљиве са опсегом блока улазе у опсег када су декларисане (нису на врху блока), излазе из подручја опсега ако се у блоку позива било која функција, враћају се назад у опсег кад се функција врати, и излази ван опсега на крају блока. У случају аутоматских локалних променљивих, они се такође алоцирају на декларацији и распоређују на крају блока, док се за статичке локалне променљиве додељују код иницијализације програма и деалоцирају по завршетку програма.

Следећи програм демонстрира променљиву, са опсегом блока који улази у опсег делом кроз блок, а затим излази из опсега када се блок заврши:

#include <stdio.h>
int main(void)
{
   char x = 'm';
   printf("%c\n", x);
   {
       printf("%c\n", x);
       char x = 'b';
       printf("%c\n", x);
   }
   printf("%c\n", x);
}
Излаз из програма
m
m
b
m

Постоје и други нивои опсега у С-у.[12] Имена променљивих која се користе у прототипу функције имају видљивост прототипа функције и опсег излаза на крају прототипа функције. Пошто се име не користи, ово није корисно за компилацију, али може бити корисно за документацију. Називи ознака за ГОТО израз имају опсег функције, док називи случајева за изјаве прекидача имају опсег блока.

C++[уреди | уреди извор]

Све променљиве које намеравамо да користимо у програму морају бити декларисане са својим типом спецификатора у ранијој тачки кода, као што смо то радили у претходном коду на почетку тела главне функције када смо прогласили да б, и резултат су типа инт. Променљива може бити глобалног или локалног опсега. Глобална променљива је променљива која је декларисана у главном телу изворног кода, изван свих функција, док је локална променљива декларисана у телу функције или блоку.

Go[уреди | уреди извор]

Go је лексички обухваћен помоћу блокова.

Java[уреди | уреди извор]

Класа у Javi може имати 3 типа променљивих.[13]

Локалне променљиве су дефинисане унутар методе, или унутар партикуларног блока. Ове променљиве су локалне до места где су дефинисан. На пример, петља унутар методе може користити локалне променљиве те методе, али не и обрнуто. Променљиве петље (локалне за ту петљу) се уништавају чим се петља заврши.

Променљиве чланице такође се називају и пољима, су променљиве декларисане унутар класе, изван било које методе. Ове променљиве су подразумевано доступне за све методе унутар те класе као и за све класе у пакету.

Параметри су променљиве у декларацији методе.

Уопштено, скуп заграда дефинише одређени опсег, али променљиве на највишем нивоу унутар класе могу се разликовати у понашању у зависности од кључних речи модификатора који су кориштени у њиховој дефиницији. Следећа табела показује приступ члановима које дозвољава сваки модификатор.[14]

JavaScript[уреди | уреди извор]

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

JavaScript има лексички опсег[16] угнежден на функцијком нивоу, при чему је глобални опсег крајњи опсег. Овај опсег користи се за и з апроменљиве и за функције. Блокирање опсега помоћу кључних речи let и const је стандардно од ECMAScript 6.. Блок-опсег се може произвести тако што се цео блок замота у функцију, а затим извршити; ово је познато као образац израза функције (IIFE) који се одмах позива.

Иако је опсег JavaScript-а једноставан - лексички, на нивоу функције - придружена правила за иницијализацију и решавање имена узрокују збрку. Прво, додељивање имену које није у опсегу подразумева креирање нове глобалне променљиве, а не локалне. Друго, да бисте креирали нову локалну променљиву, морате користити кључну реч var; променљива се тада креира на врху функције, при чему је вредност недефинисана и променљивој се додељује њена вредност када се постигне израз доделе.

На пример, следећи код производи дијалог са недефинисаним излазом, будући да је декларација локалне променљиве подигнута, сенчивши глобалну променљиву, али иницијализација није, тако да је променљива недефинисана када се користи:

a = 1;
function f() {
  alert(a);
  var a = 2;
}
f();

Надаље, како су функције првокласни објекти у ЈаваСцрипт-у и често се додељују као повратни позиви или се враћају из функција, када се функција изврши, резолуција имена зависи од места где је првобитно дефинисана а не лексичко окружење или извршно окружење одакле се позива. Угнеждени опсези одређени у ЈаваСцрипт-у, посебно затворења, који се користи као повратни позив, понекад се називају и ланцем опсега, аналогно прототипном ланцу објекта. Затворења се могу произвести у ЈаваСцрипт-у коришћењем угнеждених функција, јер су функције првокласни објекти.[17] Враћање угнеждене функције из ограђујуће функције укључује локалне променљиве функције ограђивања као лексичко окружење враћене функције, што доводи до затворења. На пример:

function newCounter() {
  // brojac koji se inkrementira na svaki poziv(pocinje od 0) 
  // i vraca novodobijenu vrednost
  var a = 0;
  var b = function() { a++; return a; };
  return b;
}
c = newCounter();
alert(c() + ' ' + c());  // izlazi "1 2"

Затворења се често користе у ЈаваСцрипт-у јер се користе за повратне позиве. Заправо, свако спајање функције у локалном окружењу као повратни позив или враћање из функције ствара затворење ако у функционом телу постоји било која невезана  променљива, ово може бити случајно. Приликом креирања повратног позива на основу параметара, параметри се морају чувати у затворењу, иначе ће случајно створити затворење које се односи на променљиве у окружењу, које се могу променити.[18]

Lisp[уреди | уреди извор]

Lisp дијалекти имају разна правила за опсег.

Оригинални Lisp је користио динамички опсег инспирисан Алголом.

Maclisp је подразумевано користио динамички опсег у интерпретатору и лексички опсег у компајлираном коду, мада је компајлирани код могао да приступи динамичким везама користећи спрецијалне декларације за одређене променљиве.[19] Међутим Maclisp је лексичко везивање третирао више као оптимизацију него што би се то могло очекивати у модерним језицима. Одвојена операција, *FUNCTION, била је доступна за неспретно решавање неких проблема.[20]

Common Lisp је усвојио лексички опсег из Scheme.[21]

ISLISP има лексички опсег за обичне променљиве. Такође има динамичке променљиве, али су у свим случајевима експлицитно обележене; морају бити дефинисани посебним специјалним обликом.

Неки други дијалекти Лиспа, попут Emacs Lisp и даље користе динамички опсег према заданим поставкама. Emacs Lisp сада има на располагању лексички опсег на бази бафера.[22]

Python[уреди | уреди извор]

За променљиве, Пајтон има опсег функција, опсег модула и глобални опсег. Имена улазе у опсег на почетку контекста, а излазе из опсега када се позива не-угнеждена функција или се контекст завршава. Ако се име користи пре иницијализације променљиве, то ствара изузетак током извођења. Ако се некој променљивој једноставно приступи у контексту, резолуција имена следи ЛЕГБ правило. Међутим, ако је променљива додељена, она подразумева стварање локалне променљиве, која је у опсегу за цео контекст. Оба ова правила могу се надјачати глобалном или не-локалном декларацијом пре употребе, што омогућава приступ глобалним променљивим чак и ако постоји интервенирајућа не-локална променљива, и додељивање глобалних или нелокалних променљивих.

Као једноставан пример, функција решава променљиву у глобалном опсегу:

>>> def f():
... print(x)
...
>>> x = 'global'
>>> f()
global

Имајте на уму да је x иницијализиран пре него што се позове f, тако да се не подиже грешка, иако је декларисана након декларисања f. Лексички је ово референца унапред, што је дозвољено у Python-у. Овде задатак ствара нову локалну променљиву, која не мења вредност глобалне променљиве:

>>> def f():
...     x = 'f'
...     print(x)
...
>>> x = 'global'
>>> print(x)
global
>>> f()
f
>>> print(x)
global

Додељивање променљивих унутар функције узрокује да се она прогласи локалном за функцију, па на тај начин коришћењем пре доделе јавља грешку. Ово се разликује од C-а, где је локална променљива само у декларацији, а не за целу функцију. Овај код ствара грешку:

>>> def f():
...     print(x)
...     x = 'f'
...
>>> x = 'global'
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment

Подразумевана правила за резолуцију имена могу се надјачати глобалном или нелокалном кључном речју. У коду испод, глобална декларација x у g значи да се x слаже са глобалном променљивом. Тако му се може приступити (као што је већ иницијализирано) и додељивању се додељује глобална променљива, уместо да се декларише нова локална променљива. Имајте на уму да није потребна глобална декларација у ф-будући да не додељује променљивој, она подразумева резолуцију за глобалну променљиву. Имајте на уму да није потребна глобална декларација у f ; будући да се не  додељује променљивој, она подразумева решавање за глобалну променљиву.

>>> def f():
...     print(x)
...
>>> def g():
...     global x
...     print(x)
...     x = 'g'
...
>>> x = 'global'
>>> f()
global
>>> g()
global
>>> f()
g


global  се такође може користити за угнеждене функције. Поред тога што омогућава додељивање глобалној променљивој, као у не угнежденој функцији, ово се такође може користити за приступ глобалној променљивој у присуству нелокалне променљиве:

>>> def f():
... def g():
... global x
... print(x)
... x = 'f'
... g()
...
>>> x = 'global'
>>> f()
global

За угнеждене функције постоји и nonlocal декларација за додељивање не-локалној променљивој, слично употреби global  у не угнежденој функцији:

>>> def f():
... def g():
... nonlocal x  # Python 3 only
... x = 'g'
... x = 'f'
... g()
... print(x)
...
>>> x = 'global'
>>> f()
g
>>> print(x)
global

R[уреди | уреди извор]

R је лексички обиман језик, за разлику од других имплементација S-а где су вредности слободних променљивих одређене скупом глобалних променљивих, док у Р одређују окружење у коме је функција креирана.Околини за опсег приступа може се приступити коришћењем различитих функција које могу да симулирају искуство динамичког опсега ако програмер пожели.

no block scoping

>>> def f():
... def g():
... nonlocal x  # Python 3 only
... x = 'g'
... x = 'f'
... g()
... print(x)
...
>>> x = 'global'
>>> f()
g
>>> print(x)
global

functions have access to environment (scope) they were created in

a <- 1

f <- function() {message(a)}

f()

## 1

variables created or modified within a function stay there

a <- 1
f <- function() {
  message(a)
  a <- 2
  message(a)
}
f()
## 1
## 2
message(a)
## 1

variables created or modified within a function stay there unless assignment to enclosing environment (scope) is explicitly requested

a <- 1
f <- function() {
  message(a)
  a <<- 2
  message(a)
}
f()
## 1
## 2
message(a)
## 2

although R has lexical scoping by default, function scopes can be changed

a <- 1
f <- function() {
  message(a)
}
my_env <- new.env()
my_env$a <- 2
f()
## 1
environment(f) <- my_env
f()
## 2

Погледај још[уреди | уреди извор]

Референце[уреди | уреди извор]

  1. ^ C99 (на језику: енглески), 2020-07-14, Приступљено 2020-08-26 
  2. ^ „The Go Programming Language Specification - The Go Programming Language”. golang.org. Приступљено 2020-08-26. 
  3. ^ „CSE 341 Lecture Notes -- Lexical and Dynamic Scoping”. web.archive.org. 2015-02-07. Архивирано из оригинала 07. 02. 2015. г. Приступљено 2020-08-26. 
  4. ^ „AngularJS”. docs.angularjs.org. Приступљено 2020-08-26. 
  5. ^ W, BackusJ; L, BauerF; GreenJ; KatzC; McCarthyJ; J, PerlisA; RutishauserH; SamelsonK; VauquoisB (1960-05-01). „Report on the algorithmic language ALGOL 60”. Communications of the ACM (на језику: енглески). doi:10.1145/367236.367262. 
  6. ^ „An overview of COMMON LISP | Proceedings of the 1982 ACM symposium on LISP and functional programming”. dl.acm.org (на језику: енглески). doi:10.1145/800068.802140. Приступљено 2020-08-26. 
  7. ^ Moses, Joel (1970-06-01). „The Function of FUNCTION in LISP, or Why the FUNARG Problem Should be Called the Environment Problem” (на језику: енглески). 
  8. ^ Conferences, University of Michigan Engineering Summer (1967). Computer and Program Organization (на језику: енглески). 
  9. ^ Conferences, University of Michigan Engineering Summer (1967). Computer and Program Organization (на језику: енглески). 
  10. ^ Technology), Project MAC (Massachusetts Institute of (1970). Project MAC Progress Report (на језику: енглески). Massachusetts Institute of Technology. 
  11. ^ „Programming Language Pragmatics”. www.cs.rochester.edu. Приступљено 2020-08-26. 
  12. ^ „IBM Knowledge Center - Home of IBM product documentation”. www.ibm.com (на језику: енглески). Приступљено 2020-08-26. 
  13. ^ „Declaring Member Variables (The Java™ Tutorials > Learning the Java Language > Classes and Objects)”. docs.oracle.com. Приступљено 2020-08-26. 
  14. ^ „Controlling Access to Members of a Class (The Java™ Tutorials > Learning the Java Language > Classes and Objects)”. docs.oracle.com. Приступљено 2020-08-26. 
  15. ^ „Annotated ES5”. es5.github.io. Приступљено 2020-08-26. 
  16. ^ „Functions”. MDN Web Docs (на језику: енглески). Приступљено 2020-08-26. 
  17. ^ „Javascript Closures”. jibbering.com. Приступљено 2020-08-26. 
  18. ^ „Explaining JavaScript scope and closures - Robert's talk”. robertnyman.com. Приступљено 2020-08-26. 
  19. ^ „The Pitmanual: Declarations and the Compiler”. maclisp.info. Приступљено 2020-08-26. 
  20. ^ „The Pitmanual: The Evaluator”. www.maclisp.info. Приступљено 2020-08-26. 
  21. ^ „CLHS: Section 1.1.2”. www.lispworks.com. Приступљено 2020-08-26. 
  22. ^ „EmacsWiki: Lexical Binding”. www.emacswiki.org. Приступљено 2020-08-26.