Klatschiga Closures

Betraktar man programspråkens utveckling över en längre tidsperiod kan man se hur de evolverat genom att plocka upp “bra idéer” från andra språk.

På 60-talet utvecklades stack-baserad exekvering, vilket banade väg för lokal variabler och rekursiva funktion. 70-talet var modulariseringens tidevarv med grupperingar av funktioner i syntaktiska moduler. På 80-talet evolverade detta till egen-definierade datatyper, som sedan tog nästa steg över till objekt-orientering under 90-talet.

Det är inte alltid ett begrepp får sitt stora genombrott bara för att det är “uppfunnet”. Klass-begreppet var en fundamental del av språket SIMULA, vilket skapades redan på 60-talet, med det var först på 90-talet begreppet klasser fick sitt stora genombrott och har bildat programmeringsnorm sedan dess.

Closure och Lambda

Ett annat annat begrepp som tagit god tid på sig att “mogna” är begreppet closure, som daterar sig långt tillbaka i tiden. Faktiskt tillbaka till programspråkens barndom, men då inom mer akademiskt orienterade programspråk såsom LISP. Ursprunget till LISP är funktions-manipulerings-systemet lambda-kalkyl. Orden closure och lambda används ibland som synonymer. Men det är först under det senaste decenniet som closures har fått sitt stora genombrott och utgör ett av de viktigaste moderna programkontruktionsbegreppen. Det är en viktigt del av alla nya programspråk och införs “löpande” i en hel del gamla språk med.

Vad är en Closure?

Så vad i all världen är det då? Kort och gott, är en closure en anonym funktion. En C-programmerare kanske skulle fnysa “funktions-pekare”, men det är inte hela sanningen. Utmärkande för en closure är följande

  • Funktions uttryck
  • En parameter-lista
  • Värde-bindningar till fria variabler (icke-parameter) i funktionsuttrycket

Här är ett exempel:

int c = 3
def f = {a, b -> a**2 + b**2 - c**2}
println 'Result = ' + f(2, 5)

Variabeln f refererar en closure, som har en parameter-lista med a och b och ett funktionsuttryck som summerar kvadraterna på a och b, samt subtraherar den kvadraten på den fria variabeln c. På den tredje raden anropas f med argumenten 2 och 3, varpå resultatet 2**2 + 5**2 – 3**2 = 20 skrivs ut.

Closuren f kan skickas iväg till en funktion eller stoppas in i en tabell och exekveras någon gång senare. För att detta ska fungera måste den spara värdet av c (värdebindning), eftersom c kanske bara var en lokal variabel och är sedan länge borta när f så småningom anropas.

Kod-exemplet ovan är skrivet i JVM språket Groovy, men det finns många andra språk som har closures också. För språket Java införs closures (project Lambda) nästa år, i version 8.

Samma program, olika språk

Så, låt oss kika på lite olika closure variationer, genom att studera hur ett visst problem kan realiseras i olika programspråk. Många gånger vill man mäta tiden för att exekvera en funktion eller grupp av funktioner. Det innebär att läsa av aktuellt klockvärde, exekvera mål-uttrycket, läsa av klocka igen samt beräkna klockdifferensen. Det blir typiskt repetitiv kod, där det som skiljer sig mellan gångerna är vad som ska mätas. Det man vill är att bryta ut denna del så att resten av koden (boiler-plate code) kan återanvändas.

Groovy

Genom att definiera en funktion, som tar en closure som argument kan detta åstadkommas. Så här det ut i Groovy. Som test-funktion mäter vi tiden för att beräkna Fibonacci-talet 40, implementerat på klassiskt ineffektivt vis med dubbel-rekursion.

Funktionen elapsed() tar en led-text plus en closure som argument. Förutom att anropa closuren (i try-blocket) och returnerna resultatet, så beräknas åtgången tid som skriv ut. Själva anropet till funktionen elapsed() görs på rad 17, där den closure som skickas med innehåller det som ska mätas, dvs anropet till fibonacci(). Här använder vi oss av den fria variabeln n.

Java

Vill vi göra något liknade i Java, som ju än så länge saknar closures, så är anropet av elapsed() inuti main() betydligt mer komplicerat. Här måste vi gå via en anonym klass som implementerar gränssnittet Stmts.

Kompilering och exekvering av Java-programmet ser ut så här.

C++

En av de stora nyheterna i språket C++, via nya standarden C++11, är closures. Dock kallas de för lambda. Så här kan kan samma problemställning se ut då.

Själva anropet av elapsed() på rad 13 utgörs av closure:n/lambda:n

[=](){ return fibonacci(n); }

Den första delen ‘[=]’ anger att fria variabler (n) ska värde-anropas, den andra delen ‘()’ är en tom parameter-lista samt den tredje och sista delen är själva funktions-uttrycket. Så här kompilerar och exekverar man C++ programmet. Jag kör med Cygwin och GCC version 4.5.3.

Erlang

Till sist, låt oss kika på hur det ser ut i språket Erlang, som jag ju skrev om tidigare i veckan. Dels, lite kod-exempel och dels om dess minst sagt guppiga historia. En closure i Erlang kallas kort och gott för anonym funktion, och anges med nyckelordet ‘fun’, vilket är kul.

En closure i Erlang definieras mellan nyckelorden ‘fun’ och ‘end’. Efter ‘fun’ finns en i detta fall tom parameter-lista följt av en avskiljare ‘->’. Till höger om avskiljaren finns själva uttrycket, som även här använder sig av en fri variabel (N). Kompilering och exekvering av programmet görs med fördel inifrån ‘werl’.

Källkod

Samtliga program-exempel finns i en ZIP-.fil här