Zatvorenje (programiranje)

U programskim jezicima zatvorenje, leksičko zatvorenje ili funkcijsko zatvorenje, je tehnika za implementaciju leksički obuhvaćenog vezivanja imena u jeziku sa funkcijama prve klase. Praktično, zatvorenje je zapis koji čuva funkciju zajedno sa okruženjem.[1] Okruženje je preslikavanje koje povezuje svaku slobodnu promenljivu neke funkcije (promenljive koje se koriste kao lokalne, ali su definisane u obuhvatajućem opsegu), sa vrednošću ili referencom na koju je ime vezano kada je zatvorenje kreirano. Zatvorenje omogućava funkciji da pristupi uhvaćenim promenljivim kroz kopije vrednosti ili referenci koje se odnose na zatvorenje, čak i kada je funkcija pozvana izvan njihovih opsega.

Istorija i etimologija uredi

Koncept zatvorenja razvijen je 60-ih godina prošlog veka za mehaničku procenu izraza u λ-računu i prvi put je potpuno implementiran 1970. godine kao jezička karakteristika u programskom jeziku PAL da bi podržao leksički obuhvaćene prvoklasne funkcije.[2]Turner's section 2, note 8 contains his claim about M-expressions

Peter J. Landin je definisao termin zatvorenja kao deo okruženja i kontrolni deo koji je koristila njegova SECD mašina za procenu izraza.[3] Joel Moses je pripisao Landin-u uvođenje termina "zatvorenje" koji se odnosi na λ-izraz čije su slobodne promenljive zatvorene leksičkim okruženjem.[4][5] Zatim ovaj termin su koristili Sussman i Steele kada su definisali Scheme 1975.[6] godine, leksički obuhvaćenu varijantu LISP-a. Ovaj termin je postao široko rasprostranjen.

Anonimne funkcije uredi

Često se izraz "zatvorenje" poistovećuje sa terminom "anonimna funkcija", ali anonimna funkcija je doslovno bezimena funkcija, a zatvorenje je instanca funkcije, vrednost, čije su nelokalne promenljive vezane za vrednosti ili za lokacije skladištenja.

Sledi primer koda u programskom jeziku Pajton :

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) је затворење.

Vrednosti a i b su zatvorenja, u oba slučaja nastala vraćanjem ugneždene funkcije sa slobodnom promenljivom iz funcije ograđivanja, tako da se slobodna promenljiva vezuje za vrednost parametra x funcije ograđivanja. Zatvorenja u a i b su funcionalno identična. Razlika u implementaciji ova dva slučaja jeste što se u prvom slučaju koristi ugneždena funkcija sa imenom, a u drugom se koristi anonimna ugneždena funkcija (Python za kreiranje anonimne funcije koristi ključnu reč lambda ). Ukoliko postoji, originalno ime nije relevantno iako je korišćeno za njihovo definisanje.

Zatvaranje je vrednost kao i svaka druga, može se koristiti direktno, što je prethodno prethodno prikazano, a to se može smatrati anonimnim zatvaranjem.

Definicije ugneždenih funkcija same nisu zatvorenja, jer imaju slobodnu promenljivu koja još nije vezana. Samo jednom kada se ograđujuća funkcija proceni vrednošću za parametar, slobodan je parametar vezane ugneždene funkcije pri čemu se stvara zatvorenje, koje se zatim vraća iz funkcije zatvorenja.

Konačno, zatvaranje se razlikuje samo od funkcije sa slobodnim promenljivim kada je izvan opsega ne-lokalnih promenljivih, inače se,okruženje koje se definiše i okruženje izvršenja, podudaraju i nema šta da ih razlikuje (statičko i dinamičko vezivanje se ne može razlikovati, jer se imena odvajaju na iste vrednosti).

U sledećem primeru, funkcije sa slobodnom promenljivom x se izvode u istom okruženju u kome je x definisano.

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

def f(y):
    return x + y

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

To se najčešće postiže povratkom funkcije, pri čemu funkcija mora biti definisana u domenu ne-lokalnih promenljivih, zbog čega će njen opseg biti manji. Iako je u praksi manje zastupljeno isti efekat se može postići senčenjem promenljive.U sledećem primeru f se može posmatrati kao zatvorenje pri čemu je x u telu f vezano za x u globalnom namespace-u :

x = 0

def f(y):
    return x + y

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

g(1)

Aplikacije uredi

Upotreba zatvorenja povezana je sa jezicima na kojima su funkcije prvoklasni objekti, u kojima se funkcije mogu vratiti kao rezultat funkcija višeg reda ili proslediti kao argumenti drugim pozivima funkcija.Ako su funkcije sa slobodnim promenljivim prvoklasne,vraćanje jedne od njih će stvoriti zatvorenje. Ovo uključuje funkcionalne programske jezike kao što su Lisp I ML, kao i mnoge moderne, multi-paradigmne jezike poput Python-a I Rust-a. Zatvorenja se često koriste sa povratnim pozivima, posebno za rukovaoce događajima, kao što je to u JavaScript-u, gde se koriste za interakcije sa dinamičnom veb stranicom.

Zatvorenja se takođe mogu koristi u stilu continuation-passing za skrivanje stanja. Konstrukcije poput objekata i kontrolnih struktura mogu se implementirati zatvorenjem. U nekim jezicima zatvorenje se može dogoditi kada je funkcija definisana unutar druge funkcije, pri čemu se unutrašnja funkcija  odnosi na lokalne promenljive spoljašnje funkcije. Kada se spoljašnja funkcija izvršava, formira se zatvorenje, koje se sastoji od koda unutrašnje funkcije i referenci na bilo koju promenljivu spoljašnje funkcije koja zahteva zatvorenje.

Prvoklasne funkcije uredi

Zatvorenja se obično pojavljuju u jezicima sa prvoklasnim funkcijama - drugim rečima, takvi jezici omogućavaju funkcijama da se prenose kao argumenti, da se vraćaju iz poziva funkcija, vezuju za imena promenljivih itd. Na primer, razmotrite sledeću funkciju u Scheme:

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

U ovom primeru, lambda izraz (lambda (book) (>= (book-sales book) threshold))   se pojavljuje unutar funkcije best-selling-books.

Kada lambda izraz dobije vrednost, šema kreira zatvorenje koje se sastoji od koda za lambda izraz i reference na promenljivu praga, koja je slobodna promenljiva unutar lambda izraza.

Zatvorenje se zatim prebacuje u funkciju filter, koja ga neprestano poziva da utvrdi koje knjige treba dodati rezultujućoj listi, a koje treba odbaciti. Pošto samo zatvorenje ima referencu na prag (threshold), ono može da koristi tu promenljivu svaki put kada je filter pozove. Sam funkcijski filter može se definisati u potpuno zasebnom fajlu.

Evo istog primer prepravljenog u JavaScript-u, drugom popularnom jeziku sa podrškom za zatvorenje:

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

Ovde se umesto lambda izraza koristi ključna reč function, a umesto funkcije globalnog filtera metoda Array.filter, u suprotnom struktura i efekat koda su jedno isto. Funkcija može stvoriti zatvaranje i vratiti ga, kao u sledećem primeru:

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

Budući da zatvorenje u ovom slučaju nadmašuje izvršenje funkcije koja ga stvara, promenljive f i dk se aktiviraju nakon što se derivative funkcija vrati, iako je izvršenje napustilo njihov obim i više nisu vidljive. U jezicima bez zatvorenja, vek automatske lokalne promenljive, podudara se sa izvršavanjem stack okvira gde su deklarisane promenljive. U jezicima sa zatvorenjem, promenljive moraju da postoje sve dok bilo koje postojeće zatvorenje ima referencu na njih. Ovo je se najčešće sprovodi korišćenjem garbage kolektora.

Stanje predstavljanja uredi

Zatvorenje se može koristiti za pridruživanje funkcije skupu „privatnih“ promenljivih, koje traju nekoliko poziva funkcije. Opseg promenljive obuhvata samo funkciju zatvorenja, tako da joj se ne može pristupiti iz drugog programskog koda. Ovo je analogno privatnim promenljivim u objeknto-orijentisanom programiranju,zapravo zatvorenja su analogna tipu objekta, posebno funkcionalnim objektima, sa slobodnom javnom metodom i mnogim privatnim promenljivim.

U jezicima stanja, zatvorenja se mogu koristiti za implementaciju paradigmi za stanje reprezentacije i skrivanje informacija. Zatvorenja koja se koriste na ovaj način više nemaju referentnu transparentnost pa samim tim nisu čista funkcija, ipak, ona se obično koriste u nečistim funkcionalnim jezicima kao što je Scheme.

Ostale namene uredi

Zatvorenja imaju mnogo primena:

Zato što zatvorenja zahtevaju procenu, ona ne obavljaju ništa sve dok ne budu pozvana-mogu se koristiti za definisanje kontrolnih struktura. Na primer, sve Smaltalkove sandardne kontrolne strukture, uključujući grane i petlje definisane su korišćenjem objekata čije metode prihvataju zatvorenje. Korisnici takođe mogu lako da definišu sopstvene kontrolne strukture.

U jezicima koji implementiraju dodelu, može se proizvesti više funkcija koje se zatvaraju u istom okruženju, pri čemu im se omogućava privatna komunikacija menjanjem tog okruženja. U šemi:

(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"

Zatvorenja se mogu koristiti za implementaciju objektnih sistema.

Implementacija i teorija uredi

Zatvorenja se obično implementiraju posebnom strukturom podataka koja sadrži pokazivač na funkcijski kod, plus prikaz leksičkog okruženja funkcije u trenutku kada je zatvorenje kreirano.

Referncijalno okruženje vezuje nelokalna imena za odgovarajuće promenljive u leksičkom okruženju u vreme kada je stvoreno zatvorenje, pri čemu dodatno produžuju njihov vek najmanje ѕa vek samog zatvorenja. Kada se zatvorenje kasnije unese, verovatno sa drugačijim leksičkim okruženjem, funkcija se izvršava svojim ne-lokalnim promenljivim koje se odnose na one koje je zarobilo zatvorenje, a ne trenutno okruženje.

Implementacija jezika ne može lako da podrži potpuno zatvorenje ako model memorije alocira sve automatske promenljive na linearni stek. U takvim jezicima, automatska lokalna promenljiva funkcije je dealocirana kada se funkcija vrati. Međutim, zatvorenje zahteva da reference slobodnih promenljivih prežive izvršenje funkcije ograđivanja. Zbog toga, te promenljive moraju biti alocirane tako da postoje sve dok više ne budu potrebne i njihovim radnim vekom mora se upravljati tako da prežive sva zatvorenja koja se odnose na njih i koja se više ne koriste.

Ovo objašnjava zašto, obično, jezici koji podržavaju zatvorenja takođe koriste sakupljanje smeća. Alternativama se upravlja manuelnom memorijom ne-lokalnih promennjivih ili, alociranjem steka, za jezik koji prihvata da će određeni slučajevi dovesti do nedefinisanog ponašanja, zbog pokazivača na oslobođene promenljive, kao u lambda izrazima ili ugneždenim funkcijama. Problem funkcionalnog argumenta opisuje poteškoće u implementaciji funkcija kao prvoklasnih objekata u programskom jeziku poput C ili C++.[7] Slično u D1 verziji, pretpostavlja se da programer zna šta treba učiniti sa delegatima i automatskim lokalnim promenljivim, jer će njihove reference biti nevažeće nakon povratka iz opsega definicije – to i dalje omogućava mnogo korisnih funkcionalnih obrazaca, ali za složene slučajeve potrebna je alokacija promenljivih. Verzija D2 je ovo rešila tako što je oktrila koje promenljive se moraju čuvati na steku, i vršiti automatsku alokaciju. Budući da D[8] koristi garbage collection, u obe verzije, nema potrebe za praćenjem upotrebe promenljivih kada se proslede.

U strogim funkcionalnim jezicima sa nepromenljivim podacima, vrlo je lako implementirati automatsko upravljanje memorijom, jer ne postoje ciklusi u referencama promenljivih. Na primer u Erlangu, svi argumenti i promenljive su alocirani na hrpi, ali reference na njih se dodatno čuvaju na steku. Reference su i dalje važeće nakon funkcijskog vraćanja. Čišćenjem hrpe upravlja garbage collector.

U ML, lokalne promenljive su u leksičkom opsegu, i tako definišu stack-like model, ali budući da su vezane za vrednosti, a ne za objekte, implementacija je slobodna za kopiranje tih vrednosti u strukturu podataka zatvorenja na način koji je nevidljiv programeru.

Zatvorenja su usko povezana sa akterima u Aktorovom modelu istovremenih računanja gde se vrednosti u leksičkom okruženju funkcije nazivaju poznanstvima. Važno pitanje za zatvorenje u konkurentnim programskim jezicima je da li se promenljive u zatvorenju mogu ažurirati i, ako mogu, kako se ažuriranja mogu sinhronizovati. Akteri pružaju jedno rešenje.[9]

Razlike u semantici uredi

Leksičko okruženje uredi

Kako različiti jezici nemaju uvek definiciju leksičkog okruženja, njihove definicije zatvorenja mogu takođe varirati. Uobičajena minimalistička definicija leksičkog okruženja definiše je kao skup svih vezivanja promenljivih u opsegu, a to je i ono što zatvorenja u svakom jeziki moraju zatvoriti. Međutim, značenje vezivanja promenljive takođe se razlikuje. U imperativnim jezicima, promenljive se vezuju za relativne lokacije u memoriji koje mogu čuvati vrednosti. Iako se relativna lokacija vezivanja ne menja tokom izvođenja, vrednost na navedenoj lokaciji može. U takvim jezicima budući da zatvorenje zatvori vezivanje, bilo koja operacija na promenljivoj, bilo da se radi o zatvorenju ili ne, izvodi se na istoj relativnoj memorijskoj lokaciji. To se često naziva hvatanje promenljive „referencom“. Evo primera koji ilustruje jedan od takvih jezika:

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)

Funkcija foo i zatvorenja na koje se odnose promenljive f i g koriste istu relativnu memorijsku lokaciju oznaćenu lokalnom promenljivom x. U nekim instancama gore navedeno ponašanje može biti nepoželjno i potrebno je vezati drugačije leksičko zatvorenje. Ponovo, u ESMA skripty, ovo se može predstaviti korišćenjem Function.bind().

Primer: Navođenje nevezane promenljive[11] uredi

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

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

Primer 2: Slučajna referenca na vezanu promenljivu uredi

Za ovaj primer očekivano ponašanje bi bilo da svaki link emitira svoj id kada se klikne; ali zbog toga što je promenljiva e ograničena na gornji opseg i lenjo se procenjuje na klik, ono što se zapravo događa je da svaki događaj pri kliktanju emitira id poslednjeg elementa u „elementima“ vezanom na kraju for petlje.[12]

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

I ovde bi promenljivu e trebalo vezati za područje bloka koristeći handle.bind(this) ili ključnu reč let.

S druge strane, mnogi funkcionalni jezici, poput ML, vezuju promenljive direktno za vrednosti. U ovom slučaju, budući da ne postoji način da se promeni vrednost promenljive jednom kada je vezana, nema potrebe da se stanje deli između zatvorenja-one zapravo koriste iste vrednosti. To se često naziva hvatanje promenljive „po vrednosti“. Javina lokalne i anonimne klase takođe spadaju u ovu kategoriju – one zahtevaju da zarobljene lokalne promenljive budu konačne, što takođe znaci da nema potrebe za deljenjem stanja.

Neki jezici vam omogućavaju da birate između hvatanja vrednosti promenljive ili njene lokacije. Na primer, u C++, zarobljene promenljive su ili deklarisane sa [&], što znači zarobljene referencom, ili sa [=], što znači zarobljene po vrednostima.

Još jedan podskup, lenjih funkcionalnih jezika poput Haskela, vezuju promenljive na rezultat budućeg računanja, a ne na vrednosti:

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

Vezivanje r zarobljeno zatvorenjem definisanim u funkciji foo je za izračunavanje (x/y)-što je u ovom slučaju rezultirano deljenjem sa 0. Međutim, budući da je računanje zarobljeno, a ne vrednost, greška se manifestuje samo kada se poziva na zatvorenje i zapravo pokušava da koristi zarobljeno vezivanje.

Napuštanje zatvorenja uredi

Još razlika manifestuje se u ponašanju drugih leksički obuhvaćenih konstruktora, kao što su  return, break i continue. Takvi konstruktori mogu se uopšteno razmotriti u smislu izazivanja nastavka escape utvrđenog od strane kontrolne izjave. U nekim jezicima, kao što je ECMASkript, povratak se odnosi na nastavak uspostavljen zatvorenjem, leksički najdublji u odnosu na izjavu – dakle, povratak unutar zatvorenja prenosi kontrolu na kod koji ga je pozvao.

Međutim, u Smaltalku, površno sličan operator ^ poziva nastavak escape uspostaljen za pozivanje metode, pri čemu se zanemaruje nastavak escape bilo kojeg ugneždenog zatvorenja. Nastavak escape određenog zatvorenja može se pozvati smao u Smaltalku pri dostizanju kraja zatvorenja. Sledeći primeri u ECMASkript i Smaltalku ističu razliku:

"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

Isečci koda ponašaće se drugačije jer Smaltalk operator ^ i JavaSkript return operator nisu analogni. U ESMASkript primeru, vraćanje h ostaviće unutrašnje zatvorenje kako bi započela nova iteracija  forEach petlje, dok će u Smaltalk primeru ^h prekinuti petlju i vratiti se iz metode foo. Uobičajeni  Lisp pruža konstruktor koji može izraziti bilo koju od gore navedenih radnji: Lisp (return-from foo x )se ponaša kao Smaltalk ^h, dok se Lisp (return-from nil x) ponaša kao JavaSkript return x.

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

Kada se poziva zatvorenje koje vraća metoda foo, ono pokušava da vrati vrednost iz poziva metode foo koja je stvorila zatvorenje. Kako se taj poziv već vratio i model Smaltalk metode ne sledi spaghetti stack disciplinu da olakša višestruki povratak, ova operacija rezultira greškom. Primer u 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

U ovom primeru i Proc.new i lambda su načini za kreiranje zatvorenja, ali tako stvorena semantika zatvorenja se razlikuje u odnosu na return statement. U Scheme, definicija i opseg return control statement su izričiti. Sledi direktan prevod primera  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"

Zatvorenja poput konstruktora uredi

Neki jezici imaju karakteristike koje simuliraju ponašanje zatvorenja. U jezicima kao što su Java, C++, Objective-C, C#, VB.NET, i D, ove karakteristike su rezultat jezičke objektno orijentisane paradigme.

Povratni pozivi (S) uredi

Neke biblioteke C-a podržavaju povratne pozive. Ovo se ponekad implementira pružanjem dve vrednosti prilikom registrovanja povratnog poziva u biblioteci: funkcijski pokazivač i odvojeni pokazivač  void* na proizvoljne podatke po izboru korisnika. Kada biblioteka funkciju povratnog poziva, ona prolazi duž pokazivača na podatke. Ovo omogućava da povratni poziv održava stanje i odnosi se na podatke zarobljene u vreme kada je registrovan u biblioteku. Idiom je sličan zatvorenju po funkcionalnosti, ali nije u sintaksi. Pokazivač void * nije bezbedan za tip, tako da se ovaj C idiom razlikuje od bezbednog zatvorenja u C#, Haskell ili ML.

Povratni pozivi se široko koriste u GUI Widget alatima za implementaciju programiranja zasnovanog na povezivanju grafičkih funkcija sa specifičnim funkcijama aplikacije koje implementiraju specifično željeno ponašanje za aplikaciju.

Ugneždena funkcija i pokazivač funkcije (S) uredi

Sa gcc ekstenzijom, može se koristiti ugneždena funkcija i pokazivač funkcije može oponašati zatvorenje, pod pretpostavkom da funkcija ne postoji. Primer ispod je nevažeći:

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

Lokalne klase i lambda funkcije (Java) uredi

Java omogućava da se klase definišu unutar metoda. One se nazivaju lokalnim klasama. Kada takve klase nisu imenovane, prepoznaju se kao anonimne klase. Lokalna klasa može se pozivati na imena u leksički ograđenim klasama, ili može samo čitati promenljive u leksički ograđenoj metodi.

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

Snimanje final (konačnih)  promenljivih omogućava vam snimanje pormenljivih po vrednosti. Čak i ako promenljiva koju želite da snimite nije konačna, uvek je možete kopirati u privremenu konačnu promenljivu neposredno pre klase.

Snimanje promenljivih prema referenci može se emulirati koristeći konačnu referencu na izmenljivi kontejner, na primer, niz sa jednim elementom. Lokalna klasa neće moći da promeni vrednost referenci kontejnera, ali će moći da promeni sadržaj kontejnera.

Sa pojavom lambda izraza zatvorenje uzrokuje da se gornji kod izvrši kao:

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

Lokalne klase su jedna od vrsta unutrašnje klase koja je deklarisana u telu metode. Java takođe podržava unutrašnje klase koje su deklarirane kao nestatični članovi klase koja se ograđuje.[13] Obično ih nazivaju „unutrašnjim klasama“.[14] One su definisane u telu klase koja se ograđuje i imaju puno pravo pristupa instanciranju promenljivih klase koja se ograđuje. Zbog njihovog vezivanja za promenljive, unutrašnja klasa se moze instancirati samo eksplicitnim vezivanjem za instancu klase koja se ograđuje koristeći posebnu sintaksu.

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

Po izvršenju, ovo će štampati cele brojeve od 0 do 9. Pazite da vas ne zbuni ova vrsta klase sa ugneždenom klasom, koja je deklarisana na isti način uz korišćenje statičkog modifikatora. One nemaju željeni efekat, već su samo klase bez posebnog vezivanja definisanog u klasi koja se ne ograđuje. Kao i kod Java 8, Java podržava funkcije kao prvoklasne objekte. Lambda izrazi ovog oblika smatraju se tipom funkcija <T,U> pri čemu je T domen, a U vrsta slike.

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

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

Blokovi (C, C++, Objective-C 2.0) uredi

Apple je predstavio blokove kao oblik zatvorenja, kao nestandardno proširenje  u  C, C++, Objective-C 2.0 i u  Mac OS X 10.6 „Snow Leopard“ i iOS 4.0. Apple je svoju implementaciju stavio na raspolaganje za GCC i clang kompajlere. Pokazivači za blokiranje i blokiranje literala označeni su sa ^. Normalne lokalne promenljive se hvataju po vrednosti kada se kreira blok, i čitljive su samo unutar bloka. Promenljive koje treba uhvatiti referencom označene su sa __block. Blokove koji treba da ostanu izvan opsega u kome su stvoreni biće potrebno kopirati.[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());

Delegati (C#, VB.NET, D) uredi

C#  anonimne metode i lambda izrazi podržavaju zatvorenje:

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

Visual Basic .NET koji ima brojne jezičke karakteristike slične onima u C#, takođe podržava lambda izraze sa zatvorenjem:

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

U D,zatvorenja implementiraju delegati, funkcijski pokazivač uparena sa pokazivačem 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 verzija, ima ograničenu podršku za zatvorenje. Na primer, kod iznad neće raditit ispravno, jer se promenljiva a nalazi na steku, a nakon povratka iz test(), neće biti važeća. Ovo se može rešiti eksplicitnim alociranjem promenljive „a“ na hrpu, ili korišćenjem struktura ili klasa za skladištenje svih potrebnih zatvorenih promenljivih. Zatvorenja se mogu proslediti na druge funkcije, sve dok se koriste za vreme dok su referentne vrednosti važeće, i korisne su za pisanje generičkog kod za obradu podataka, tako da ovo ograničenje u praksi često nije problem.

Ovo ograničenje je ispravljeno u D2 verziji – promenljiva „a“ biće automatski alocirana na hrpi, jer se koristi u unutrašnjoj funkciji, a delegat te funkcije može napustiti trenutni opseg. Sve ostale promenljive koje nius reference delegata ili koje su referencirane samo delegatima koji ne mogu izbći trenutni opseg, ostaju na steku, što je jednostavnije i brže od alociranja hrpe. Isto važi i za unutrašnje metode klase koje referenciraju promenljive funkcije.

Funkcionalni objekti  (C++) uredi

C++ omogućava definisanje objekata funkcije preko operatora overloading. Ovi objekti se ponašaju poput funkcija u funkcionalnom programskom jeziku. Oni mogu biti kreirani tokom izvođenja i mogu sadržati stanje, ali ne podrazumevaju zatvaranje lokalnih promenljivih kao što je to slučaj kod zatvorenja. Od revizije 2011, C++ takođe podržava zatvorenja, koja su tip funkcijskog objekta koji se stvara automatski iz posebne jezičke konstrukcije koja se zove lambda izraz. C++ zatvorenje može zatvoriti njegov kontekst čuvanjem kopija promenljivih kao članove objekta zatvorenja ili reference. U kasnijem slučaju, ako objekt zatvorenja izađe iz opsega  referenciranog objekta, pozivanje njegovog operatora izaziva nedefinisano ponašanje jer zatvorenja u  C++ ne produžavaju vek njihovog konteksta.

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) uredi

Eiffel uključuje Inline agente koji definišu zatvorenje. Inline agent je objekat koji predstavlja rutinu, definisan unošenjem koda u liniju rutine. Na primer:

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

Argument je agent, koji pretstavlja postupak sa 2 argumenta; postupak ponalazi zemlju na odgovarajućim koordinatama i prikazuje ih. Čitav agent je“pretplaćen“ na tip događaja

C++ Builder __ zatvorenjem rezervisana reč uredi

Embarcadero C++Builder pruža rezervnu reč__zatvorenje da bi omogućio pokazivač na metodu sa sličnom sintaksom kao funkcijski pokazivač.

U standardnom C možete napisati typedef za pokazivač na tip funkcije koristeći sintaksu:

typedef void (*TMyFunctionPointer)( void );

Na sličan način možete deklarisati typedef za pokazivač na metodu koristeći sledeću sintaksu:

typedef void (__closure *TMyMethodPointer)();

Vidi još uredi

Reference uredi

  1. ^ „Page:Scheme - An interpreter for extended lambda calculus.djvu/22 - Wikisource, the free online library”. en.wikisource.org. Pristupljeno 6. 8. 2020. 
  2. ^ Funkcionalno programiranje (na jeziku: srpski), 5. 4. 2020, Pristupljeno 26. 8. 2020 
  3. ^ Peter Landin (na jeziku: engleski), 6. 8. 2020, Pristupljeno 27. 8. 2020  The mechanical evaluation of expressions
  4. ^ Joel Moses (na jeziku: engleski), 1. 7. 2020, Pristupljeno 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 (na jeziku: engleski), Pristupljeno 27. 8. 2020  https://en.wikipedia.org/wiki/International_Standard_Book_Number
  6. ^ Gerald Jay Sussman (na jeziku: engleski), 22. 8. 2020, Pristupljeno 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. Pristupljeno 26. 8. 2020. 
  8. ^ D (programming language) (na jeziku: engleski), 28. 7. 2020, Pristupljeno 6. 8. 2020 
  9. ^ Clinger, Will (jun 1981). Foundations of Actor Semantics. 
  10. ^ ECMAScript (na jeziku: srpski), 23. 1. 2020, Pristupljeno 6. 8. 2020 
  11. ^ „Function.prototype.bind()”. MDN Web Docs (na jeziku: engleski). Pristupljeno 6. 8. 2020. 
  12. ^ „Closures”. MDN Web Docs (na jeziku: engleski). Pristupljeno 6. 8. 2020. 
  13. ^ „oracle”. [mrtva veza]
  14. ^ „Inner Class Example (The Java™ Tutorials > Learning the Java Language > Classes and Objects)”. docs.oracle.com. Pristupljeno 6. 8. 2020. 
  15. ^ „Introduction”. developer.apple.com. Pristupljeno 6. 8. 2020. 
  16. ^ „Programming with C Blocks on Apple Devices”. web.archive.org. 25. 10. 2010. Arhivirano iz originala 25. 10. 2010. g. Pristupljeno 6. 8. 2020.