Closures i programmering — vad är en closure? Definition och exempel

Lär dig vad en closure är i programmering: tydlig definition, hur closures bevarar miljöer, skillnad mot anonyma funktioner och praktiska exempel.

Författare: Leandro Alegsa

Inom datavetenskap är en closure en funktion som har en egen miljö. I denna miljö finns det minst en bunden variabel (ett namn som har ett värde, t.ex. ett tal). I stängningens miljö hålls de bundna variablerna i minnet mellan användningarna av stängningen.

Peter J. Landin gav denna idé namnet closure 1964. Programmeringsspråket Scheme gjorde closures populära efter 1975. Många programmeringsspråk som skapats efter den tiden har closures.

Anonyma funktioner (funktioner utan namn) kallas ibland felaktigt för closures. De flesta språk som har anonyma funktioner har också closures. En anonym funktion är också en closure om den har en egen miljö med minst en bunden variabel. En anonym funktion utan egen miljö är inte en closure. En namngiven closure är inte anonym.

Vad menas med "miljö" och "bunden variabel"?

Miljön är de variabler och deras värden som är tillgängliga för funktionen när den skapades. En bunden variabel är en variabel som inte är definierad inuti den inre funktionen utan i den omgivande kontexten (det yttre funktionsanropet eller omgivande blocket). När den inre funktionen refererar till sådana variabler säger man att den fångar dem — det är detta som gör funktionen till en closure.

Lexikal skopning (lexical scoping)

Closures bygger normalt på lexikal skopning, dvs. variabler binds utifrån var koden skrevs, inte var den körs. Det innebär att en funktion alltid ser de variabler som fanns i dess omgivning när den definierades, även om funktionen körs senare, i ett annat sammanhang.

Enkla exempel

Nedan följer exempel i JavaScript och Python som visar hur closures används i praktiken.

// JavaScript: räknefunktion som minns sitt eget tillstånd function makeCounter() {   let count = 0;   return function() {     count++;     return count;   }; } const c = makeCounter(); console.log(c()); // 1 console.log(c()); // 2 
# Python: funktioner som fångar variabler från omgivningen def make_counter():     count = 0     def counter():         nonlocal count         count += 1         return count     return counter  c = make_counter() print(c())  # 1 print(c())  # 2 

Vanliga fallgropar

En typisk fälla uppstår när man skapar funktioner i loopar och tror att varje funktion får sin egen kopia av loopvariabeln. I vissa språk (eller med gamla konstruktioner) delas bindningen, vilket leder till oväntade resultat.

// JavaScript (problematiskt i äldre kod med var) var funcs = []; for (var i = 0; i < 3; i++) {   funcs.push(function() { return i; }); } console.log(funcs[0]()); // 3 (alla returnerar 3) 

Lösningar är att använda blockscoped variabler (let i modern JavaScript) eller skapa en wrapperfunktion som binder värdet:

// JavaScript med let (varje iteration får egen i) let funcs = []; for (let i = 0; i < 3; i++) {   funcs.push(function() { return i; }); } console.log(funcs[0]()); // 0 
# Python: "late binding" i lambdas inom loop funcs = [] for i in range(3):     funcs.append(lambda: i) print(funcs[0]())  # 2 (alla returnerar 2)  # Lösning: bind värdet som standardargument funcs = [] for i in range(3):     funcs.append(lambda i=i: i) print(funcs[0]())  # 0 

När är en anonym funktion en closure?

En anonym funktion blir en closure först när den fångar värden från sin omgivning. Exempelvis är en anonym funktion som enbart räknar med sina egna lokala parametrar inte en closure. Däremot blir den en closure om den refererar till variabler utanför sig själv som måste bevaras mellan anrop.

Användningsområden

  • Data-inkapsling: Closures kan skapa privata variabler och funktioner (enkelt sätt att implementera information hiding utan klasser).
  • Callbacks och asynkron kod: Closures används ofta när man skickar funktioner som callback som behöver komma åt viss kontext.
  • Delvis applicering och currying: Skapa nya funktioner med en del av argumenten förifyllda.
  • Generatorer och iteratorer: Bevara körstatus mellan anrop (i språk utan inbyggda generatorer används closures för detta).

Prestanda och minne

Eftersom en closure håller referenser till variabler i sin miljö kan dessa variabler inte samlas upp av skräpsamlare innan closure inte längre används. Det är oftast inte ett problem, men i långlivade closures med stora strukturer kan det leda till högre minnesanvändning. Var därför medveten om vad som fångas — ibland räcker det att fånga ett primitivt värde istället för en hel datastruktur.

Skillnaden mellan closure och anonym funktion — kortfattat

En anonym funktion är bara en funktion utan namn. En closure är en funktion (anonym eller namngiven) tillsammans med dess bevarade omgivning. Alla closures är funktioner, men inte alla funktioner är closures.

Sammanfattning

Closures är ett kraftfullt och vanligt verktyg i modern programmering som låter funktioner bära med sig sin omgivning. De möjliggör privat tillstånd, flexibla callbacks och funktionell stil som partial application. Samtidigt kräver de att man är medveten om variabelbindningar och livstidsaspekter för att undvika buggar och onödig minnesanvändning.

Stängningar och förstklassiga funktioner

Värden kan vara siffror eller någon annan typ av data, t.ex. bokstäver, eller datastrukturer som består av enklare delar. I reglerna för ett programmeringsspråk är första klassens värden värden som kan ges till funktioner, returneras av funktioner och bindas till ett variabelnamn. Funktioner som tar emot eller returnerar andra funktioner kallas för funktioner av högre ordning. De flesta språk som har funktioner som första klassens värden har också funktioner av högre ordning och closures.

Ta till exempel en titt på följande Scheme-funktion:

; Återge en lista över alla böcker som har sålts i minst THRESHOLD-exemplar. (define (best-selling-books threshold) (filter (lambda (book) (>= (book-sales book) threshold)) book-list)))

I det här exemplet är lambdauttrycket (lambda (book) (>= (book-sales book) threshold)) en del av funktionen best-selling-books. När funktionen körs måste Scheme göra värdet av lambdauttrycket. Det gör det genom att skapa en closure med koden för lambda och en referens till variabeln threshold, som är en fri variabel inom lambda. (En fri variabel är ett namn som inte är bundet till ett värde).

Filterfunktionen kör sedan avslutningen på varje bok i listan för att välja vilka böcker som ska returneras. Eftersom stängningen själv har en referens till tröskelvärdet kan stängningen använda det värdet varje gång filter kör stängningen. Själva funktionen filter kan skrivas i en helt separat fil.

Här är samma exempel omskrivet i ECMAScript (JavaScript), ett annat populärt språk med stöd för closures:

// Återge en lista med alla böcker som sålts i minst "tröskelvärdet". function bestSellingBooks(threshold) { return bookList. filter( function(book) { return book. sales >= threshold; }     ); }

ECMAScript använder ordet funktion i stället för lambda och metoden Array.filter i stället för filterfunktionen, men i övrigt gör koden samma sak på samma sätt.

En funktion kan skapa en stängning och returnera den. Följande exempel är en funktion som returnerar en funktion.

I systemet:

; Återge en funktion som approximerar derivatan av f ; med hjälp av ett intervall av dx, som bör vara lämpligt litet. (definiera (derivatan f dx) (lambda (x) (/ (- (f (+ x dx))) (f x))) dx)))))

I ECMAScript:

// Återge en funktion som approximerar derivatan av f // med hjälp av ett intervall av dx, som bör vara lämpligt litet. function derivative(f, dx) { return function(x) { return (f(x + dx) - f(x)) / dx; }; }; }

I stängningsmiljön behålls de bundna variablerna f och dx efter det att den omslutande funktionen (derivatan) har återgått. I språk utan closures skulle dessa värden gå förlorade efter att den omslutande funktionen har återvänt. I språk med closures måste en bunden variabel behållas i minnet så länge som en closure har den.

En stängning behöver inte bildas med hjälp av en anonym funktion. Programmeringsspråket Python har till exempel begränsat stöd för anonyma funktioner, men det har stängningar. Ovanstående ECMAScript-exempel skulle till exempel kunna implementeras i Python på följande sätt:

# Återge en funktion som approximerar derivatan av f # med hjälp av ett intervall av dx, som bör vara lämpligt litet. def derivata(f, dx): def gradient(x): return (f(x + dx) - f(x)) / dx return gradient

I det här exemplet utgör funktionen gradient en slutenhet tillsammans med variablerna f och dx. Den yttre omslutande funktionen derivativ returnerar denna slutenhet. I det här fallet skulle en anonym funktion också fungera.

def derivative(f, dx): return lambda x: (f(x + dx) - f(x)) / dx

Python måste ofta använda namngivna funktioner i stället eftersom dess lambdauttryck endast kan innehålla andra uttryck (kod som returnerar ett värde) och inte påståenden (kod som har effekter men inget värde). Men i andra språk, t.ex. Scheme, returnerar all kod ett värde; i Scheme är allting ett uttryck.

Användning av stängningar

Stängningar har många användningsområden:

  • Utvecklare av programvarubibliotek kan låta användarna anpassa beteendet genom att skicka closures som argument till viktiga funktioner. En funktion som sorterar värden kan t.ex. acceptera ett stängningsargument som jämför de värden som ska sorteras enligt ett användardefinierat kriterium.
  • Eftersom closures fördröjer utvärderingen - dvs. de "gör" ingenting förrän de anropas - kan de användas för att definiera kontrollstrukturer. Till exempel definieras alla Smalltalks standardkontrollstrukturer, inklusive grenar (if/then/else) och slingor (while och for), med hjälp av objekt vars metoder accepterar closures. Användare kan också enkelt definiera sina egna kontrollstrukturer.
  • Flera funktioner kan produceras som är nära varandra i samma miljö, vilket gör det möjligt för dem att kommunicera privat genom att ändra miljön (på språk som tillåter detta).

I systemet

(definiera foo #f) (definiera bar #f) (låt ((secret-message "none")) (set! foo (lambda (msg) (set! secret-message msg))) (set! bar (lambda () secret-message))) (display (bar))) ; skriver ut "none" (newline) (foo "meet me by the docks at midnight") (display (bar)) ; skriver ut "meet me by the docks at midnight"
  • Closures kan användas för att implementera objektsystem.

Anmärkning: Vissa talare kallar alla datastrukturer som binder en lexikalisk miljö för en closure, men termen hänvisar vanligtvis specifikt till funktioner.

Frågor och svar

F: Vad är en avslutande examen i datavetenskap?


S: En closure är en funktion som har en egen miljö.

F: Vad innehåller miljön i en slutenhet?


S: Miljön för en slutenhet innehåller minst en bunden variabel.

Fråga: Vem gav idén om slutning sitt namn?


Svar: Peter J. Landin gav namnet till stängningsidén 1964.

Fråga: Vilket programmeringsspråk gjorde closures populära efter 1975?


Svar: Programmeringsspråket Scheme gjorde stängningar populära efter 1975.

Fråga: Är anonyma funktioner och closures samma sak?


S: Anonyma funktioner kallas ibland felaktigt för closures, men alla anonyma funktioner är inte closures.

Fråga: Vad gör en anonym funktion till en closure?


Svar: En anonym funktion är en closure om den har en egen miljö med minst en bunden variabel.

Fråga: Är en namngiven slutning anonym?


Svar: Nej, en namngiven stängning är inte anonym.


Sök
AlegsaOnline.com - 2020 / 2025 - License CC3