Затворење (програмирање)

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

Историја и етимологија уреди

Концепт затворења развијен је 60-их година прошлог века за механичку процену израза у λ-рачуну и први пут је потпуно имплементиран 1970. године као језичка карактеристика у програмском језику PAL да би подржао лексички обухваћене првокласне функције.[2]Turner's section 2, note 8 contains his claim about M-expressions

Peter J. Landin је дефинисао термин затворења као део окружења и контролни део који је користила његова SECD машина за процену израза.[3] Joel Moses је приписао Landin-у увођење термина "затворење" који се односи на λ-израз чије су слободне променљиве затворене лексичким окружењем.[4][5] Затим овај термин су користили Sussman и Steele када су дефинисали Scheme 1975.[6] године, лексички обухваћену варијанту LISP-а. Овај термин је постао широко распрострањен.

Анонимне функције уреди

Често се израз "затворење" поистовећује са термином "анонимна функција", али анонимна функција је дословно безимена функција, а затворење је инстанца функције, вредност, чије су нелокалне променљиве везане за вредности или за локације складиштења.

Следи пример кода у програмском језику Пајтон :

def f(x):
    def g(y):
        return x + y
    return g  # Враћа затворење.

def h(x):
    return lambda y: x + y  # Враћа затворење..

# Додељује специфично затворење променљивој.
a = f(1)
b = h(1)

# Коришћење затворења складиштеног у променљивој.
assert a(5) == 6
assert b(5) == 6

# Коришћење затворења без везивања за променљиву прво
assert f(1)(5) == 6  # f(1) је затворење.
assert h(1)(5) == 6  # h(1) је затворење.

Вредности a и b су затворења, у оба случаја настала враћањем угнеждене функције са слободном променљивом из фунције ограђивања, тако да се слободна променљива везује за вредност параметра x фунције ограђивања. Затворења у a и b су фунционално идентична. Разлика у имплементацији ова два случаја јесте што се у првом случају користи угнеждена функција са именом, а у другом се користи анонимна угнеждена функција (Python за креирање анонимне фунције користи кључну реч lambda ). Уколико постоји, оригинално име није релевантно иако је коришћено за њихово дефинисање.

Затварање је вредност као и свака друга, може се користити директно, што је претходно претходно приказано, а то се може сматрати анонимним затварањем.

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

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

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

x = 1
nums = [1, 2, 3]

def f(y):
    return x + y

map(f, nums)
map(lambda y: x + y, nums)

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

x = 0

def f(y):
    return x + y

def g(z):
    x = 1  
    return f(z)

g(1)

Апликације уреди

Употреба затвoрења повезана је са језицима на којима су функције првокласни објекти, у којима се функције могу вратити као резултат функција вишег реда или проследити као аргументи другим позивима функција.Ако су функције са слободним променљивим првокласне,враћање једне од њих ће створити затворење. Ово укључује функционалне програмске језике као што су Lisp I ML, као и многе модерне, мулти-парадигмне језике попут Python-a I Rust-a. Затворења се често користе са повратним позивима, посебно за руковаоце догађајима, као што је то у JavaScript-u, где се користе за интеракције са динамичном веб страницом.

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

Првокласне функције уреди

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

(define (best-selling-books threshold)
  (filter
    (lambda (book)
      (>= (book-sales book) threshold))
    book-list))

У овом примеру, ламбда израз (lambda (book) (>= (book-sales book) threshold))   се појављује унутар функције best-selling-books.

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

Затворење се затим пребацује у функцију filter, која га непрестано позива да утврди које књиге треба додати резултујућој листи, а које треба одбацити. Пошто само затворење има референцу на праг (threshold), оно може да користи ту променљиву сваки пут када је filter позове. Сам функцијски filter може се дефинисати у потпуно засебном фајлу.

Ево истог пример преправљеног у JavaScript-u, другом популарном језику са подршком за затворење:

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Овде се уместо ламбда израза користи kључна реч function, а уместо функције глобалног филтера метода Array.filter, у супротном структура и ефекат кода су једно исто. Функција може створити затварање и вратити га, као у следећем примеру:

// Return a function that approximates the derivative of f
// using an interval of dx, which should be appropriately small.
function derivative(f, dx) {
  return function (x) {
    return (f(x + dx) - f(x)) / dx;
  };
}

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

Стање представљања уреди

Затворење се може користити за придруживање функције скупу „приватних“ променљивих, које трају неколико позива функције. Опсег променљиве обухвата само функцију затворења, тако да јој се не може приступити из другог програмског кода. Ово је аналогно приватним променљивим у објекнто-оријентисаном програмирању,заправо затворења су аналогна типу објекта, посебно функционалним објектима, са слободном јавном методом и многим приватним променљивим.

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

Остале намене уреди

Затворења имају много примена:

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

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

(define foo #f)
(define bar #f)

(let ((secret-message "none"))
  (set! foo (lambda (msg) (set! secret-message msg)))
  (set! bar (lambda () secret-message)))

(display (bar)) ; штампа "none"
(newline)
(foo "meet me by the docks at midnight")
(display (bar)) ; штампа "meet me by the docks at midnight"

Затворења се могу користити за имплементацију објектних система.

Имплементација и теорија уреди

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

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

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

Ово објашњава зашто, обично, језици који подржавају затворења такође користе сакупљање смећа. Алтернативама се управља мануелном меморијом не-локалних променњивих или, алоцирањем стека, за језик који прихвата да ће одређени случајеви довести до недефинисаног понашања, због показивача на ослобођене променљиве, као у ламбда изразима или угнежденим функцијама. Проблем функционалног аргумента описује потешкоће у имплементацији функција као првокласних објеката у програмском језику попут C или C++.[7] Слично у Д1 верзији, претпоставља се да програмер зна шта треба учинити са делегатима и аутоматским локалним променљивим, јер ће њихове референце бити неважеће након повратка из опсега дефиниције – то и даље омогућава много корисних функционалних образаца, али за сложене случајеве потребна је алокација променљивих. Верзија Д2 је ово решила тако што је октрила које променљиве се морају чувати на стеку, и вршити аутоматску алокацију. Будући да Д[8] користи garbage collection, у обе верзије, нема потребе за праћењем употребе променљивих када се проследе.

У строгим функционалним језицима са непроменљивим подацима, врло је лако имплементирати аутоматско управљање меморијом, јер не постоје циклуси у референцама променљивих. На пример у Ерлангу, сви аргументи и променљиве су алоцирани на хрпи, али референце на њих се додатно чувају на стеку. Референце су и даље важеће након функцијског враћања. Чишћењем хрпе управља garbage collector.

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

Затворења су уско повезана са актерима у Акторовом моделу истовремених рачунања где се вредности у лексичком окружењу функције називају познанствима. Важно питање за затворење у конкурентним програмским језицима је да ли се променљиве у затворењу могу ажурирати и, ако могу, како се ажурирања могу синхронизовати. Актери пружају једно решење.[9]

Разлике у семантици уреди

Лексичко окружење уреди

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

ECMAScript [10]

// ECMAScript
var f, g;
function foo() {
  var x;
  f = function() { return ++x; };
  g = function() { return --x; };
  x = 1;
  alert('inside foo, call to f(): ' + f());
}
foo();  // 2
alert('call to g(): ' + g());  // 1 (--x)
alert('call to g(): ' + g());  // 0 (--x)
alert('call to f(): ' + f());  // 1 (++x)
alert('call to f(): ' + f());  // 2 (++x)

Функција foo и затворења на које се односе променљиве f и g користе исту релативну меморијску локацију ознаћену локалном променљивом x. У неким инстанцама горе наведено понашање може бити непожељно и потребно је везати другачије лексичко затворење. Поново, у ЕСМА скриптy, ово се може представити коришћењем Function.bind().

Пример: Навођење невезане променљиве[11] уреди

var module = {
  x: 42,
  getX: function() {return this.x; }
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // функција је позвана у глобалном опсегу

var boundGetX = unboundGetX.bind(module); 
console.log(boundGetX());

Пример 2: Случајна референца на везану променљиву уреди

За овај пример очекивано понашање би било да сваки линк емитира свој ид када се кликне; али због тога што је променљива е ограничена на горњи опсег и лењо се процењује на клик, оно што се заправо догађа је да сваки догађај при кликтању емитира ид последњег елемента у „елементима“ везаном на крају фор петље.[12]

var elements= document.getElementsByTagName('a');
//Нетачно: е је везано за функцију која садржи фор петљу, не затворење
for (var e in elements){ e.onclick=function handle(){ alert(e.id);} }

И овде би променљиву е требало везати за подручје блока користећи handle.bind(this) или кључну реч let.

С друге стране, многи функционални језици, попут МЛ, везују променљиве директно за вредности. У овом случају, будући да не постоји начин да се промени вредност променљиве једном када је везана, нема потребе да се стање дели између затворења-оне заправо користе исте вредности. То се често назива хватање променљиве „по вредности“. Јавина локалне и анонимне класе такође спадају у ову категорију – оне захтевају да заробљене локалне променљиве буду коначне, што такође знаци да нема потребе за дељењем стања.

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

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

-- Haskell

-- Haskell
foo :: Fractional a => a -> a -> (a -> a)
foo x y = (\z -> z + r)
          where r = x / y

f :: Fractional a => a -> a
f = foo 1 0

main = print (f 123)

Везивање р заробљено затворењем дефинисаним у функцији foo је за израчунавање (x/y)-што је у овом случају резултирано дељењем са 0. Међутим, будући да је рачунање заробљено, а не вредност, грешка се манифестује само када се позива на затворење и заправо покушава да користи заробљено везивање.

Напуштање затворења уреди

Још разлика манифестује се у понашању других лексички обухваћених конструктора, као што су  return, break и continue. Такви конструктори могу се уопштено размотрити у смислу изазивања наставка escape утврђеног од стране контролне изјаве. У неким језицима, као што је ECMASkript, повратак се односи на наставак успостављен затворењем, лексички најдубљи у односу на изјаву – дакле, повратак унутар затворења преноси контролу на код који га је позвао.

Међутим, у Смалталку, површно сличан оператор ^ позива наставак escape успостаљен за позивање методе, при чему се занемарује наставак escape било којег угнежденог затворења. Наставак escape одређеног затворења може се позвати смао у Смалталку при достизању краја затворења. Следећи примери у ECMASkript и Смалталку истичу разлику:

"Smalltalk"
foo
  | xs |
  xs := #(1 2 3 4).
  xs do: [:x | ^x].
  ^0
bar
  Transcript show: (self foo printString) "prints 1"
// ECMAScript
function foo() {
  var xs = [1, 2, 3, 4];
  xs.forEach(function (x) { return x; });
  return 0;
}
alert(foo()); // prints 0

Исечци кода понашаће се другачије јер Смалталк оператор ^ и ЈаваСкрипт return оператор нису аналогни. У ЕСМАСкрипт примеру, враћање х оставиће унутрашње затворење како би започела нова итерација  forEach петље, док ће у Смалталк примеру ^х прекинути петљу и вратити се из методе foo. Уобичајени  Lisp пружа конструктор који може изразити било коју од горе наведених радњи: Lisp (return-from foo x )се понаша као Смалталк ^х, док се Lisp (return-from nil x) понаша као ЈаваСкрипт return x.

"Smalltalk"
foo
    ^[ :x | ^x ]
bar
    | f |
    f := self foo.
    f value: 123 "error!"

Када се позива затворење које враћа метода foo, оно покушава да врати вредност из позива методе foo која је створила затворење. Како се тај позив већ вратио и модел Смалталк методе не следи spaghetti stack дисциплину да олакша вишеструки повратак, ова операција резултира грешком. Пример у Ruby:

# Ruby

def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call 
  return "return from foo"
end

def bar
  f = lambda { return "return from lambda" }
  f.call 
  return "return from bar"
end

puts foo 
puts bar

У овом примеру и Proc.new и lambda су начини за креирање затворења, али тако створена семантика затворења се разликује у односу на return statement. У Scheme, дефиниција и опсег return control statement су изричити. Следи директан превод примера  Ruby.

; Scheme
(define call/cc call-with-current-continuation)

(define (foo)
  (call/cc
   (lambda (return)
     (define (f) (return "return from foo from inside proc"))
     (f) 
     (return "return from foo"))))

(define (bar)
  (call/cc
   (lambda (return)
     (define (f) (call/cc (lambda (return) (return "return from lambda"))))
     (f) 
     (return "return from bar"))))

(display (foo)) ; штампа "return from foo from inside proc"
(newline)
(display (bar)) ;штампа "return from bar"

Затворења попут конструктора уреди

Неки језици имају карактеристике које симулирају понашање затворења. У језицима као што су Java, C++, Objective-C, C#, VB.NET, и D, ове карактеристике су резултат језичке објектно оријентисане парадигме.

Повратни позиви (С) уреди

Неке библиотеке Ц-а подржавају повратне позиве. Ово се понекад имплементира пружањем две вредности приликом регистровања повратног позива у библиотеци: функцијски показивач и одвојени показивач  void* на произвољне податке по избору корисника. Када библиотека функцију повратног позива, она пролази дуж показивача на податке. Ово омогућава да повратни позив одржава стање и односи се на податке заробљене у време када је регистрован у библиотеку. Идиом је сличан затворењу по функционалности, али није у синтакси. Показивач void * није безбедан за тип, тако да се овај Ц идиом разликује од безбедног затворења у C#, Haskell или ML.

Повратни позиви се широко користе у GUI Widget алатима за имплементацију програмирања заснованог на повезивању графичких функција са специфичним функцијама апликације које имплементирају специфично жељено понашање за апликацију.

Угнеждена функција и показивач функције (С) уреди

Са gcc екстензијом, може се користити угнеждена функција и показивач функције може опонашати затворење, под претпоставком да функција не постоји. Пример испод је неважећи:

#include <stdio.h>

typedef int (*fn_int_to_int)(int); //тип фунцкције int->int

fn_int_to_int adder(int number) {
    int add (int value) { return value + number; }
    return &add; // & operator је опционалан овде јер име саме фунцкије у С-у јесте показивач на саму себе
}
     
int main(void) {
    fn_int_to_int add10 = adder(10);
    printf("%d\n", add10(1));
    return 0;
}

Локалне класе и ламбда функције (Јава) уреди

Јава омогућава да се класе дефинишу унутар метода. Оне се називају локалним класама. Када такве класе нису именоване, препознају се као анонимне класе. Локална класа може се позивати на имена у лексички ограђеним класама, или може само читати променљиве у лексички ограђеној методи.

class CalculationWindow extends JFrame {
    private volatile int result;
    ...
    public void calculateInSeparateThread(final URI uri) {
        // Израз "new Runnable() { ... }" је анонимна класа која имплементира 'Runnable' интерфејс.
        new Thread(
            new Runnable() {
                void run() {
                    // Може читати финалну локалну променљиву
                    calculate(uri);
                    // Може приступити приватним пољима класе:
                    result = result + 10;
                }
            }
        ).start();
    }
}

Снимање final (коначних)  променљивих омогућава вам снимање порменљивих по вредности. Чак и ако променљива коју желите да снимите није коначна, увек је можете копирати у привремену коначну променљиву непосредно пре класе.

Снимање променљивих према референци може се емулирати користећи коначну референцу на изменљиви контејнер, на пример, низ са једним елементом. Локална класа неће моћи да промени вредност референци контејнера, али ће моћи да промени садржај контејнера.

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

class CalculationWindow extends JFrame {
    private volatile int result;
    ...
    public void calculateInSeparateThread(final URI uri) {
        // Код () -> { /* code */ } представља затворење.
        new Thread(() -> {
                calculate(uri);
                result = result + 10;
        }).start();
    }
}

Локалне класе су једна од врста унутрашње класе која је декларисана у телу методе. Јава такође подржава унутрашње класе које су деклариране као нестатични чланови класе која се ограђује.[13] Обично их називају „унутрашњим класама“.[14] Оне су дефинисане у телу класе која се ограђује и имају пуно право приступа инстанцирању променљивих класе која се ограђује. Због њиховог везивања за променљиве, унутрашња класа се мозе инстанцирати само експлицитним везивањем за инстанцу класе која сe ограђује користећи посебну синтаксу.

public class EnclosingClass {
    /* Define the inner class */
    public class InnerClass {
        public int incrementAndReturnCounter() {
            return counter++;
        }
    }

    private int counter;
    {
        counter = 0;
    }

    public int getCounter() {
        return counter;
    }

    public static void main(String[] args) {
        EnclosingClass enclosingClassInstance = new EnclosingClass();
        /*Инстанцирање унутрашње класе са везивањем на инстанцу*/
        EnclosingClass.InnerClass innerClassInstance =
            enclosingClassInstance.new InnerClass();

        for (int i = enclosingClassInstance.getCounter(); (i =
        innerClassInstance.incrementAndReturnCounter()) < 10;) {
            System.out.println(i);
        }
    }
}

По извршењу, ово ће штампати целе бројеве од 0 до 9. Пазите да вас не збуни ова врста класе са угнежденом класом, која је декларисана на исти начин уз коришћење статичког модификатора. Оне немају жељени ефекат, већ су само класе без посебног везивања дефинисаног у класи која се не ограђује. Као и код Јава 8, Јава подржава функције као првокласне објекте. Ламбда изрази овог облика сматрају се типом функција <T,U> при чему је Т домен, а U врста слике.

public static void main(String[] args) {
    Function<String, Integer> length = s -> s.length();

    System.out.println( length.apply("Hello, world!") ); //Штампаће 13.
}

Блокови (C, C++, Objective-C 2.0) уреди

Apple је представио блокове као облик затворења, као нестандардно проширење  у  C, C++, Objective-C 2.0 и у  Mac OS X 10.6 „Snow Leopard“ и iOS 4.0. Apple је своју имплементацију ставио на располагање за GCC и clang компајлере. Показивачи за блокирање и блокирање литерала означени су са ^. Нормалне локалне променљиве се хватају по вредности када се креира блок, и читљиве су само унутар блока. Променљиве које треба ухватити референцом означене су са __block. Блокове који треба да остану изван опсега у коме су створени биће потребно копирати.[15][16]

typedef int (^IntBlock)();

IntBlock downCounter(int start) {
	 __block int i = start;
	 return [[ ^int() {
		 return i--;
	 } copy] autorelease];
}

IntBlock f = downCounter(5);
NSLog(@"%d", f());
NSLog(@"%d", f());
NSLog(@"%d", f());

Делегати (C#, VB.NET, D) уреди

C#  анонимне методе и ламбда изрази подржавају затворење:

var data = new[] {1, 2, 3, 4};
var multiplier = 2;
var result = data.Select(x => x * multiplier);

Visual Basic .NET који има бројне језичке карактеристике сличне онима у C#, такође подржава ламбда изразе са затворењем:

Dim data = {1, 2, 3, 4}
Dim multiplier = 2
Dim result = data.Select(Function(x) x * multiplier)

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

auto test1() {
    int a = 7;
    return delegate() { return a + 3; }; 
}

auto test2() {
    int a = 20;
    int foo() { return a + 5; } // унутрашња функција
    return &foo;

void bar() {
    auto dg = test1();
    dg();    // =10
    dg = test2();
    dg();    // =25   
}

D1 верзија, има ограничену подршку за затворење. На пример, код изнад неће радитит исправно, јер се променљива а налази на стеку, а након повратка из test(), неће бити важећа. Ово се може решити експлицитним алоцирањем променљиве „а“ на хрпу, или коришћењем структура или класа за складиштење свих потребних затворених променљивих. Затворења се могу проследити на друге функције, све док се користе за време док су референтне вредности важеће, и корисне су за писање генеричког код за обраду података, тако да ово ограничење у пракси често није проблем.

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

Функционални објекти  (C++) уреди

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

Main article: Anonymous function § C++ (since C++11)

void foo(string myname) {
    int y;
    vector<string> n;
    // ...
    auto i = std::find_if(n.begin(), n.end(),
               //  lambda израз:
               [&](const string& s) { return s != myname && s.size() > y; }
             );
    // 'i' је сада или 'n.end()' или показује на прво 'n'
    // које није једнако 'myname' и чија је дужина већа од 'y'
}

Inline agents (Eiffel) уреди

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

ok_button.click_event.subscribe (
	agent (x, y: INTEGER) do
		map.country_at_coordinates (x, y).display
	end
)

Аргумент је агент, који претставља поступак са 2 аргумента; поступак поналази земљу на одговарајућим координатама и приказује их. Читав агент је“претплаћен“ на тип догађаја

C++ Builder __ затворењем резервисана реч уреди

Embarcadero C++Builder пружа резервну реч__затворење да би омогућио показивач на методу са сличном синтаксом као функцијски показивач.

У стандардном C можете написати typedef за показивач на тип функције користећи синтаксу:

typedef void (*TMyFunctionPointer)( void );

На сличан начин можете декларисати typedef за показивач на методу користећи следећу синтаксу:

typedef void (__closure *TMyMethodPointer)();

Види још уреди

Референце уреди

  1. ^ „Page:Scheme - An interpreter for extended lambda calculus.djvu/22 - Wikisource, the free online library”. en.wikisource.org. Приступљено 6. 8. 2020. 
  2. ^ Функционално програмирање (на језику: српски), 5. 4. 2020, Приступљено 26. 8. 2020 
  3. ^ Peter Landin (на језику: енглески), 6. 8. 2020, Приступљено 27. 8. 2020  The mechanical evaluation of expressions
  4. ^ Joel Moses (на језику: енглески), 1. 7. 2020, Приступљено 27. 8. 2020  https://en.wikipedia.org/wiki/AI_Memo A useful metaphor for the difference between FUNCTION and QUOTE in LISP is to think of QUOTE as a porous or an open covering of the function since free variables escape to the current environment. FUNCTION acts as a closed or nonporous covering (hence the term "closure" used by Landin). Thus we talk of "open" Lambda expressions (functions in LISP are usually Lambda expressions) and "closed" Lambda expressions. [...] My interest in the environment problem began while Landin, who had a deep understanding of the problem, visited MIT during 1966–67. I then realized the correspondence between the FUNARG lists which are the results of the evaluation of "closed" Lambda expressions in LISP and ISWIM's Lambda Closures.
  5. ^ Book sources (на језику: енглески), Приступљено 27. 8. 2020  https://en.wikipedia.org/wiki/International_Standard_Book_Number
  6. ^ Gerald Jay Sussman (на језику: енглески), 22. 8. 2020, Приступљено 27. 8. 2020  and Guy L. Steele Jr. (December 1975), Scheme: An Interpreter for the Extended Lambda Calculus
  7. ^ „function - What is the difference between a 'closure' and a 'lambda'?”. Stack Overflow. Приступљено 26. 8. 2020. 
  8. ^ D (programming language) (на језику: енглески), 28. 7. 2020, Приступљено 6. 8. 2020 
  9. ^ Clinger, Will (јун 1981). Foundations of Actor Semantics. 
  10. ^ ECMAScript (на језику: српски), 23. 1. 2020, Приступљено 6. 8. 2020 
  11. ^ „Function.prototype.bind()”. MDN Web Docs (на језику: енглески). Приступљено 6. 8. 2020. 
  12. ^ „Closures”. MDN Web Docs (на језику: енглески). Приступљено 6. 8. 2020. 
  13. ^ „oracle”. [мртва веза]
  14. ^ „Inner Class Example (The Java™ Tutorials > Learning the Java Language > Classes and Objects)”. docs.oracle.com. Приступљено 6. 8. 2020. 
  15. ^ „Introduction”. developer.apple.com. Приступљено 6. 8. 2020. 
  16. ^ „Programming with C Blocks on Apple Devices”. web.archive.org. 25. 10. 2010. Архивирано из оригинала 25. 10. 2010. г. Приступљено 6. 8. 2020.