Inden for datalogi er en lukning en funktion, der bærer sit eget miljø med sig. I dette miljø findes mindst én bundet variabel (et navn, der har en værdi, f.eks. et tal), og miljøet gemmer disse bundne variabler i hukommelsen mellem kald af funktionen. En lukning bevarer altså referencerne til de variable, der var tilgængelige, da lukningen blev oprettet, så de fortsat kan bruges, selv om det oprindelige kaldende sted er færdigt.

Idéen fik navnet lukning af Peter J. Landin i 1964. Programmeringssproget Scheme var med til at gøre lukninger almindelige i sprogdesign efter 1975, og de fleste moderne sprog understøtter i dag mekanismer, der svarer til lukninger.

Anonyme funktioner (funktioner uden navn) bliver nogle gange fejlagtigt omtalt som synonyme med closures. Mange sprog, der tilbyder anonyme funktioner, tilbyder også lukninger. En anonym funktion er kun en lukning, hvis den har sit eget miljø med mindst én bundet variabel. Omvendt kan en navngiven funktion også være en lukning, hvis den f.eks. oprettes dynamisk og fanger omgivelser.

Formel og praktisk forståelse

Formelt er en lukning en første-klasse funktion sammen med et miljø (en bindingsgraf eller en række navne-værdi-par), der binder de frie variable i funktionen. Praktisk betyder det, at når en funktion defineres inde i en anden funktion og refererer til den ydre funktions variable, får den en indkapslet tilstand, der kan overleve efter, at den ydre funktion er afsluttet.

Enkle eksempler

JavaScript:

function gørTæller() {     let count = 0;     return function() {         count += 1;         return count;     }; } const tæller = gørTæller(); console.log(tæller()); // 1 console.log(tæller()); // 2 

Her er den indre anonymfunktion en lukning — den husker og opdaterer variablen count fra det ydre miljø.

Python (med nonlocal):

def gør_tæller():     count = 0     def tæller():         nonlocal count         count += 1         return count     return tæller  t = gør_tæller() print(t())  # 1 print(t())  # 2 

Scheme (illustrativt):

(define (gør-tæller)   (let ((count 0))     (lambda ()       (set! count (+ count 1))       count))) 

Anvendelser

  • Indkapsling / datahiding: Lukninger kan erstatte objekters private felter ved at gemme data i et lukningsmiljø.
  • Konstruktionsfunktioner (function factories): Funktionen kan oplades med konfiguration og returnere specialiserede funktioner.
  • Callbacks og event-handling: Lukninger bevarer kontekst mellem registrering og udførelse af callback-kode.
  • Currying og partial application: Lukninger bruges til at binde nogle argumenter og returnere en ny funktion, der venter på resten.
  • Iterators og generator-mønstre: Intern tilstand kan holdes i lukninger frem for globale variabler.
  • Decorator-/middleware-mønstre: Lukninger gør det nemt at indkapsle ekstra opførsel omkring en eksisterende funktion.

Implementering og hukommelse

For at lukninger fungerer skal de bundne variable ikke destrueres, når det ydre funktionskald slutter. Dette kræver typisk, at disse variable flyttes fra stacken til heapen eller gemmes i en særlig miljøstruktur (activation record eller closure object). Derfor afhænger lukningers levetid ofte af sprogrun-time-systemet og garbage collection: så længe mindst én reference til lukningen findes, holdes miljøet i live.

Implementeringsdetaljer varierer: nogle sprog kopierer miljøet ved oprettelse, andre bruger delte miljørecords. Valget påvirker ydeevne (kopieringsomkostning vs. indirektion) og semantik ved mutation.

Typiske faldgruber og bedste praksis

  • Variabelfanging i løkker: I JavaScript kan brug af var i en løkke føre til, at alle lukninger fanger samme variabel. Løsningen er at bruge let/const (let) eller indkapsle per-iteration værdier i et nyt scope.
  • Uventet deling af tilstand: Når lukninger deler et miljø, kan mutation ved ét sted påvirke andre. Overvej immutability eller kopiering, hvis dette er et problem.
  • Memory leaks: Langlivede lukninger kan fastholde store datastrukturer, så vær opmærksom på referencer og ryd op, hvis nødvendigt.
  • Debugging: Hvis miljøet er komplekst, kan det være sværere at overskue tilstand – gode navne og begrænset omfang hjælper.

Yderligere bemærkninger

Lukninger er et centralt koncept i funktionel og moderne imperativ programmering. De tilbyder en simpel, kraftfuld måde at kombinere kode og data, hvilket fremmer abstraktion og genbrug. Når du arbejder med lukninger, er det nyttigt at forstå det underliggende scoping (lexical vs. dynamic scoping) og hvordan dit valgte sprog implementerer miljøer.