Java får closures med havelåger


Det bliver den såkaldte 'strawman"-syntaks, der skal flette closures ind i den kommende udgave af Java.
Closures findes i mange sprog og er en måde at benytte en lille blok af kode som parameter. Muligheden findes i mange sprog, som f.eks. Ruby og C#.
Debatten om hvorvidt Java har brug for closures, og hvordan det skal implementeres, har raset i årevis. Det er tilsyneladende et kommende parallelisme-bibliotek, som har gjort udslaget.
Når collection-klasser skal behandles parallelt, sker det ved at definere en operation, som kan udføres på collection'ens elementer uafhængigt af rækkefølge. Dermed kan operationen udføres på flere processorkerner samtidigt.
Uden closures ville parallelisme-biblioteket kræve 80 nye interfaces i JDK-bibliotekerne.
Udvikler Ole Friis Østergaard fra Trifork ser closures som en god tilføjelse til sproget:
»Jeg synes, det er rigtigt godt, for det gør, at det dagligdags arbejde med Java bliver mere smertefrit. I Ruby har man også closures. Det gør, at éns programmer føles anderledes. For eksempel er det nemmere, hvis man har en liste, hvor man vil hive et enkelt element ud, så skal man bruge meget lidt kode, hvis man har closures,« har han tidligere fortalt Version2.
En række offentliggjorte regressionstest demonstrerer anvendelsen. Her defineres en funktion, der returnerer et heltal plus én, med argumentet 3:
int i3 = #(int x)( x + 1 ).(3);
Havelågetegnet kendes fra Javadoc-dokumentation, hvor det også benyttes til at repræsentere en metode. Det samme kan også skrives med en eksplicit krop, afgrænset af tuborg-parenteser:
int i3 = #(int x){ return x + 1; }.(3);
Når en operation skal udføres på elementerne i en collection, kan det for eksempel se sådan ud:
c.map(#(int x)(x + 3));
Her adderes tallet tre med alle elementer i en heltals-collection.
Der er allerede mange stemmer, som kritiserer syntaksen.
Den danske Rails-opfinder, David Heinemeier Hansson, skriver på Twitter, at der er alt for meget støj i den nye syntaks, og det synspunkt bakkes op mange steder. Andre indvender, at der blot er tale om akademiske eksempler, men at syntaksen ser ganske tilforladelig ud til hverdagsbrug.
- Så sker det: Java får de omstridte 'closures' i 2013
- Denne artikel
- emailE-mail
- linkKopier link

...men det er dyrt at lave god journalistik. Derfor beder vi dig overveje at tegne abonnement på Version2.
Digitaliseringen buldrer derudaf, og it-folkene tegner fremtidens Danmark. Derfor er det vigtigere end nogensinde med et kvalificeret bud på, hvordan it bedst kan være med til at udvikle det danske samfund og erhvervsliv.
Og der har aldrig været mere akut brug for en kritisk vagthund, der råber op, når der tages forkerte it-beslutninger.
Den rolle har Version2 indtaget siden 2006 - og det bliver vi ved med.
Fortsæt din læsning
- Sortér efter chevron_right
- Trådet debat
Konceptet er fra 1960'erne, og er først udødeliggjort i Scheme formentlig. Det betyder en inkubationstid på en 40-50 år. Ikke dårligt!
Stort set alle andre sprog, der er værd at tale om, har closures i dag. Selv de gamle Unix-geeks valgte at smide closures i deres Go-sprog.
Syntaxen er jo bare syntax. Det væsentlige er at det ikke fylder for meget, for det dræber notationens anvendelighed. Anonyme klasser er nuttede, men de er ikke korte i notation.
Jeg synes det er skruen uden ende, hvordan Java bliver ved med at få hovsa-løsninger fordi man ikke vil gå på kompromis med bagudkompatibilitet (erasure generics anyone?).
Problemet med syntaksen er relevant synes jeg, folk har i forvejen svært ved at læse samt forstå wildcards, upper/lower bounds og co/contravariance ved generics i Java. Nu skal vi så også til at rode 3x throws keywords ind i signaturen. F.eks.:
public <T, throws E> forEach(Block<T, throws E> block) throws E;
For 12 år siden, før han startede på C#, tilføjede Anders Hejlsberg delegates til Java, hvortil Sun lagde sag an mod Microsoft og skrev artikler som denne:http://java.sun.com/docs/white/delegates.html
Så Oracle/Sun, nu Java endelig får delegates synes i så ikke det ser lidt dumt ud hvad i skrev og gjorde dengang? Nogen vil mene der ikke er noget at sige til at Sun ikke kunne holde sig kørende, de holdt desværre op med at sætte standarden.
Så Oracle/Sun, nu Java endelig får delegates synes i så ikke det ser lidt dumt ud hvad i skrev og gjorde dengang?
Hvis du læser den artikel som du selv henviser til, så går en del af Suns argumentation at deres implementering med unnamed classes holder koden der hvor den hører hjemme, mens MSs implementation kræver at den bliver lagt et andet sted hen og ovenikøbet skal have et navn. De nye closures ligger meget tættere op af Suns unnamed classes end det ligger af MSs delegates, så jeg tror ikke at Sun synes at det ser dumt ud hvad de skrev.
MSs implementation kræver at den bliver lagt et andet sted hen og ovenikøbet skal have et navn.
Det var kun sådan i den meget tidlige C# 1.0, C# 2.0 introducerede anonymous methods imens C# 3.0 fik understøttelse for fulde lambda udtryk.
For at forstå problemet med Java's krav til interface dispatch og en halvhjertet generics implementation, behøver man blot at have brug for f.eks. at lade en type implementere Comparable<? extends Enum> og Comparable for at rende ind i problemer.
For at forstå problemet med Java's krav til interface dispatch og en halvhjertet generics implementation, behøver man blot at have brug for f.eks. at lade en type implementere Comparable<? extends Enum> og Comparable for at rende ind i problemer.
Er det kun mig der får det dårligt ved at prøve at forestille mig hvad programmøren har tænkt da prøvede at implementere dette?
Når man ser problemer, som dem i tråden "Skruen uden ende", skyldes det som regel, at typesystemet ikke fra starten af er designet til at håndtere polymorfi (generics) og højereordensfunktioner.
Man får først rigtig glæde af polymorfi og højereordensfunktioner, hvis der er strukturel ækvivalens, tupeltyper og allerhelst typeinferens. Ellers drukner man i typenotation. Man kan så evt. udvide med typeklasser (som i Haskell) eller effekttyper (så man kan se bl.a. exceptions i typen), men det bør gøres med varsomhed, hvis man ikke har typeinferens, da man ellers nemt kommer til at drukne i typeangivelser.
Er det ikke bare en kortere syntax for hvad man allerede kan i Java? F.eks de nævnte eksempler:
int i3 = #(int x)( x + 1 ).(3); c.map(#(int x)(x + 3));
kunne skrives i dag:
interface ci { int cc(int x); } int i3=new ci() { int cc(int x) { return x+1 }}.cc(3); c.map(new ci() { int cc(int x) { return x+3 }});
De nye closures er en kortere notation, men funktionelt er der ikke noget nyt. Jeg har dog ikke studeret de nye closures i detaljer, så det kan da være at de kan mere end eksemplerne i artiklen viser.
Funktionelt har der ikke været noget nyt, siden det første turing komplette sprog.. ;)
Det afgørende ved en closure er at den kan referere det omkringliggende scope. Noget i retning af:
int incr = 5; int i3 = #(int x)( x + incr ).(3); c.map(#(int x)(x + incr));
Det er lidt mere besværligt med en interface-løsning, med mindre du dependency-injecter incr eller lignende. Rigtigt sjovt bliver det når closures refererer andre closures, returneres fra funktioner eller lignende ting.
Værktøjet er rart for programmøren - og endnu en ting fra de funktionelle sprog som bliver tilføjet til de imperative.
Det afgørende ved en closure er at den kan referere det omkringliggende scope....Det er lidt mere besværligt med en interface-løsning, med mindre du dependency-injecter incr eller lignende.
Unnamed classes har jo også uden videre adgang til det omkringliggende scope, så på det punkt er der ikke en gang tale om kortere syntax (eller kan undvære final i flere tilfælde?)
Scala findes jo allerede og har alle de features som java aldrig kommer til at få. Det er java-kompatibelt, typestærkt, har closures og typeinferens.
Eksempel på simple closures: scala> val c = List(1, 2, 3) c: List[Int] = List(1, 2, 3)
scala> c.map(_ + 3) res1: List[Int] = List(4, 5, 6)
scala> c.map(_ + "a") res3: List[java.lang.String] = List(1a, 2a, 3a)
Jeg vil hellere skifte til at skrive min kode i Scala end at benytte nye features i java.
Én ting er at introducere closures, men det løser jo ikke at hele økosystemet omkring java med indbyggede API'er og eksterne biblioteker jo ikke bruger closures og pga. bagud-kompatibilitetskrav heller ikke kommer til at gøre det forløbigt. Så er energien bedre brugt på at skifte sprog.
Jeg vil hellere skifte til at skrive min kode i Scala end at benytte nye features i java.
Jovist, men jeg bryder mig ikke meget om Scalas objektorienterede notation. For mig er det væsentligt mere naturligt at skrive
[code=haskell] map (+ 3) [1,2,3] [/code] end [code=scala] List(1,2,3).map (_ + 3) [/code]
" Jovist, men jeg bryder mig ikke meget om Scalas objektorienterede notation. For mig er det væsentligt mere naturligt at skrive
map (+ 3) [1,2,3]
end
List(1,2,3).map (_ + 3) "
Og hvis du vil kalde en funktion på ovenstående resultat, hvad er så mest naturligt?
filter map (+ 3) [1,2,3]
eller
List(1,2,3).map(_ + 3).filter
(første argument til filter er udeladt i begge eksempler..)
her vil jeg påstå at 2. er mere læseligt, da læseretningen følger evalueringen ( den evaluering der foregår i læserens hoved, ikke nødvendigvis den compileren dikterer, som varierer pga. lazyness etc..)
her vil jeg påstå at 2. er mere læseligt, da læseretningen følger evalueringen
Så du foretrækker også at skrive dine regnestykker som "x y z*+" i stedet for "x+y*z"?
At funktion kommer før argument har skoleelever lært siden syvende klasse, så vil ikke tro, at de vil have problemer med at forstå foranstillede funktioner i programmeringssprog. Derimod kan den omvendte notation, man finder i OO-sprog, være et problem. Desuden har OO notationen en påtvunget assymetri, der ikke altid er naturlig. Hvis du f.eks. skal finde midtpunktet mellem to punkter p og q, skal du kalde p.midpoint(q) i stedet for midpoint(p,q). Alene det, at en to-argumentsfunktion skal være en egenskab ved det ene argument, som bruges på det andet argument, er ikke intuitivt.
Så du foretrækker også at skrive dine regnestykker som "x y z*+" i stedet for "x+y*z"?
Arh, skal vi ikke blive enige om at hvis man skriver "x y z*+" i et OO sprog, så må man skrive "+*x y z" i funktionsnotation...
Begge sprogparadigmer er vist enige om at operatører som + og * kan skrives i infix notation.
I Scala er operatører faktisk funktioner på tal. Så følgende er lovlig Scala kode:
x.+(y.*(z))
Man har så indført at man kan undlade at skrive . og () i funktionskald med kun en parameter. Derfor er det ækvivalent at skrive:
x + y * z
Når man undlader parenteserne træder der regler for prioritet i kraft, så den korrekt ganger y og z før den lægger x til.
Det jeg finder fascinerende ved Scalas tilgang er at han er sluppet afsted med at definere relativt få regler, som bruges overalt. Sprogdefinitionen er faktisk meget simpel.
Alt er objekter, også konstanter som for eksempel heltal. Derfor kan man kalde diverse funktioner på dem, og det bliver så brugt til at implementere aritmetiske funktioner.
Oversætteren vil så nødvendigvis snyde og genkende forskellige konstruktioner, men det er en implementationsdetalje.
Så du foretrækker også at skrive dine regnestykker som "x y z*+" i stedet for "x+y*z"
Det er vist ikke den korrekte skrivemåde. OO: x.add(y.mult(z)) Fkt: add(x,mult(y,z))
At funktion kommer før argument har skoleelever lært siden syvende klasse,
Det argument er jo svært at hamle op med. Hvis skoleelever lærer det, så må det nødvendigvis være det bedste :)
Man vil naturligvis have nemmest ved at læse det man er vant til at læse. Men personligt er jeg ikke i tvivl om at det er nemmest hvis objektet står før operationen. Det gør det delvist i matematikken: x+y starter med et objekt mens sin(x) starter med funktionen. Og på dansk starter man oftest med objektet.
Folkene bag F# er tilsyneladende blive smittet af . notationen i deres arbejde med sproget på .net platformen, i sådan en grad at operatoren |> er blevet idiomatisk.
eksempel:
(* Print even fibs *) [1 .. 10] |> List.map fib |> List.filter (fun n -> (n % 2) = 0) |> printlist
, voila, . notation i et overvejende funktionelt sprog :)
i modsætning til:
printlist List.filter (fun n -> (n % 2) = 0) List.map fib [1 .. 10]
, vurder selv hvad der er mest forståeligt...
Det er vist ikke den korrekte skrivemåde.
OO: x.add(y.mult(z))
Fkt: add(x,mult(y,z))
Nu var det primært argumentet med, at beregningsrækkefølge = læserækkefølge, jeg kommenterede. Ingen af dine eksempler opfylder dette.
Jeg er bestemt heller ikke tilhænger af rendyrket præfixnotation. Jeg har programmeret i LISP og Scheme, og finder denne notation mindre læselig en blandet notation (som brugt i matematik).
Det argument er jo svært at hamle op med. Hvis skoleelever lærer det, så må det nødvendigvis være det bedste :)
Ikke nødvendigvis. Men man skal have en god grund til at afvige fra den notation, som skolelever har lært. LISP og Scheme har en pointe med. at det hele er mere enkel og ensartet, men det kan man ikke sige om OO notation -- med mindre den er helt rendyrket, så man skal skrive x.+(y) i stedet for x+y. Så er den i det mindste ensartet, men ikke (EMM) enklere.
Desuden er den matematiske notation et produkt af flere hundrede års udvikling, hvor man har tilstræbt læselighed og sproguafhængighed uden hensyn til mekanisering. Igen kræver det et rigtigt godt argument at smide denne notation væk, og erstatte den med noget andet.
Desuden er den matematiske notation et produkt af flere hundrede års udvikling, hvor man har tilstræbt læselighed og sproguafhængighed uden hensyn til mekanisering. Igen kræver det et rigtigt godt argument at smide denne notation væk, og erstatte den med noget andet.
Nu vil det være en overdrivelse at kalde mig for en stor matematiker, men er der nogen faste regler for hvornår man skriver funktionen først og hvornår man bruger en operator mellem argumenterne? Er reglen ikke bare at man gør det som er mest naturligt?
Som i List(1, 2, 3) map (_ + 3)
Her putter man map mellem argumenterne som en operator med det argument at map er en lige så almindelig operation på lister som + er på heltal.
Kan man tage patent på hvilken notation der er mest matematisk?
Torben du siger:
Nu var det primært argumentet med, at beregningsrækkefølge = læserækkefølge, jeg kommenterede. Ingen af dine eksempler opfylder dette.
Men Frederik siger jo netop at det er den mentale evaluering der er den vigtigste:
List(1,2,3).map(_ + 3).filter</p>
<p>her vil jeg påstå at 2. er mere læseligt, da læseretningen følger evalueringen ( den evaluering der foregår i læserens hoved, ikke nødvendigvis den compileren dikterer, som varierer pga. lazyness etc..)
Jeg kan kun tale for mig selv, men jeg tænker tag listen, læg 3 til alle elementer og filtrer den for noget.
filter map (+ 3) [1,2,3] Læsning af overstående kræver det jo at ens hjene er indrettet som en gammel HP lommeregner. ;-)
For mig er vinderargumentet for OO notationen at man kan se hvor funktionerne hører hjemme. Map og filter kommer ikke ud af det blå, men er noget en liste "kan".
Med hensyn til dit eksempel med at: p.midpoint(q) (eller p midpoint q, hvis man vil) skulle være mindre naturligt end: midpoint(p, q)
Her gælder samme argument igen. Jeg vil vide hvor min midpoint funktion kommer fra kun ved at læse den linje.
Hvis ikke man synes at instanser af punkter skal kunne beregne afstande til hinanden, så kan kan man jo bare definere en metode med to argumenter i et andet objekt.
Map og filter kommer ikke ud af det blå, men er noget en liste "kan".
Det argument viser blot, hvor gennemsyret din tankegang er af OO. Map og filter er noget, man [i]gør med[/i] en liste, ikke noget, den af sig selv [i]kan[/i].
Argh, her gik du IMHO for langt i fordanskningen ;-)ikke (EMM) enklere
@Torben
Der er nu et punkt hvor gængs OO notation er en fordel. Ikke så meget pga. OO men fordi du skriver sit subject/emne før funktionen. De var heldige med rækkefølgen i forbindelse med interaktiv hjælp i editoren :).
Ofte er det 'svære' at huske funktionen der skal bruges og ikke den værdi/ variabel som funktionen/metoden skal anvendes på - den er jo i scope og kan ses i editoren og meget ofte har du lige skrevet den.
Det er måske mindre hjælpsomt pga. type-inferens og deraf de mere generiske funktioner som ofte altid kan anvendes, f.eks. 'id' vil altid være anvendelig på en værdi. Der er sikkert mange andre problemer :)
En anden grund til, at funktionssyntaks (som i ML, Haskell, etc.) for bl.a. map er meget mere naturlig end metodekaldssyntaks (som i Scala) er, at funktionskomposition bliver helt naturlig at skrive. F.eks. i SML:
[code=ocaml] val revrev = List.rev o (List.map List.rev) val test = (revrev [[1],[2,3],[4,5,6]] = [[6,5,4],[3,2],[1]]) [/code]
PS: Har ikke lige SML installeret på den her maskine, så koden er skrevet frit efter hukommelsen, men det burde virke...
PPS: Ingen til ML. :suk:
Syntaksen er godt nok lidt mærkelig, men man kan gøre nogle spændende ting i Scala:En anden grund til, at funktionssyntaks (som i ML, Haskell, etc.) for bl.a. map er meget mere naturlig end metodekaldssyntaks (som i Scala) er, at funktionskomposition bliver helt naturlig at skrive. F.eks. i SML:
[code=java] implicit def toRevrevT = new AnyRef { def revrev = o.reverse map (_ reverse) } [/code] Herefter har alle lister en metode kaldet revrev:
[code=java]
scala> List(List(1),List(2,3),List(4,5,6)) revrev
res0: List[List[Int]] = List(List(6, 5, 4), List(3, 2), List(1))
[/code]
Det er rigtigt at det bliver lidt længere i Scala. Lister er ikke hardwired in i sproget, men bliver understøttet af standardbiblioteket.
def revrev(list : List[List[_]]) = for (sublist <- list.reverse) yield sublist.reverse val test = revrev(List(List(1), List(2, 3), List(4, 5, 6)))
Målet med Scala er ikke at være det mest rendyrkede funktionsprogrammeringssprog i verden. Her gør SML et godt stykke arbejde.
Scala bygger oven på SML og Java og skaber en hybrid som understøtter både funtionel og imperativ tankegang på en måde som gør begge dele naturlige. Kompromisset er så at den funktionelle syntaks bliver lidt længere. Fordelen er så at det er kompatibelt med Java - både rent teknisk og konceptuelt.
Scala er ikke det ultimative sprog (her skal vi nok ud og opfinde ét sprog pr. udvikler), men jeg synes at det er et fantastisk godt bud på et kompromis som man kan bygge systemer på de næste ti år.
Syntax er jo smag og behag. I de fleste tilfælde synes jeg faktisk at Scala's for-comprehensions er lettere at læse en closures.
scala> val c = List(1, 2, 3) c: List[Int] = List(1, 2, 3)
scala> c.map(_ + 3) res1: List[Int] = List(4, 5, 6)
scala> for (x <- c) yield x + 3 res5: List[Int] = List(4, 5, 6)
for/yield syntaksen er bare sukker for map. Personligt tror jeg er lettere at "sælge" til alle dem omkring mig som aldrig har set andet end imperativ programmering, især hvis det bliver en tand mere kompliceret end dette eksempel.
[code=scala] for (x <- c) yield x + 3 [/code]
Haskell er mere læselig:
[code=haskell] [x+3 | x<-c] [/code]
PPS: Ingen
til ML. :suk:</p> </blockquote> code=ocaml kan til nød bruges også til SML.
Én ting er at introducere closures, men det løser jo ikke at hele økosystemet omkring java med indbyggede API'er og eksterne biblioteker jo ikke bruger closures og pga. bagud-kompatibilitetskrav heller ikke kommer til at gøre det forløbigt.
Det samme var tilfældet for generics, men det lykkedes alligevel at få de fleste til at skifte til at bruge det generiske API, selv om man stadig kunne/kan bruge det "gamle" ikke-generiske API. Så det er ikke umuligt, at man kan få de fleste til at bruge et nyt API med closures.
Så det er ikke umuligt, at man kan få de fleste til at bruge et nyt API med closures.
Og hvis Sun/Oracle implementerer det så closurenotationen kan bruges hvor der ellers forventes en implementation af et interface med kun en funktion, så vil dele af de eksisterende APIer kunne acceptere closures uden ændringer. Eksemplet:
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("Hello, world!"); } });
Kunne så skrives:
button.addActionListener(#(ActionEvent e) { System.out.println("Hello, world!"); })
Hej Niels
bare et lille hurtigt tak for et forståeligt eksempel! Den syntaks kan jeg godt se fornuften i - lidt ligesom da man indførte for(objekt obj : kollektion)-notationen.
Mvh Jan
Jeg har fulgt diskussionen på lambda-dev. Sprogdesignerne har specifikt sagt at "havelåge" syntaksen er en pladsholder for den endelige syntaks.
Havelågen er valgt for at gøre det enklere at implementere en parser lige nu. Der er nemlig meget større problemer end syntaks når man vil designe "closures" til Java. Fx. er de bekymrede for at der opstår "huller" i Javas typesystem - hvor oversætteren ikke kan finde og rapportere typefejl (eller mulige typefejl).
Som Casper Bang er inde på lader Javas generics lidt tilbage at ønske. Og ironisk nok er det lige netop samspillet mellem 3 Java "valg" som giver problemer lige nu: Javas [i]array covariance[/i] (som C# desværre adopterede), checked exceptions og [i]type erasure[/i] generics.
Det hører også med, at de forslag der diskuteres nu ikke er fulde closures som de andre sprog implementerer dem, eller som wikipedia definerer [i]closure[/i]: "
".In computer science, a closure is a first-class function with free variables that are bound in the lexical environment
De closures som diskuteres for Java vil nemlig have samme begrænsning mht. brug af variable fra [i]lexical scope[/i] som anonyme indre klasser har: De kan kun referere [i]final[/i] variable.
Uffe, nu du har fulgt diskussionen lidt nærmere, kan du så sige om notationen som Niels nævner bliver tilladt: button.addActionListener(#(ActionEvent e) { System.out.println("Hello, world!"); })
Som jeg forstår det kræver den syntax jo at sproget bliver i stand til at caste et vilkårligt interface med en metode i til en closure - eller er det omvendt?
kan du så sige om notationen som Niels nævner bliver tilladt:
button.addActionListener(#(ActionEvent e) { System.out.println("Hello, world!"); })
Ja - der arbejdes med implicitte konverteringer mellem funktionstyperne og SAMs (SAM = Single Abstract Method) - dvs. abstrakte klasser med en enkelt abstrakt metode eller et interface med en enkelt metode. Det er mit indtryk at det ikke vil blive samme type, men at disse konverteringer vil blive defineret så det eksisterende klassebibliotek kan udnyttes bedre.
kan du så sige om notationen som Niels nævner bliver tilladt: button.addActionListener(#(ActionEvent e) { System.out.println("Hello, world!"); })
Jeg skrev ikke at den blev tilladt. jeg skrev at det var en mulighed for Sun/Oracle at implementere det, men jeg har ingen anelse om de vil gøre det.
Som jeg forstår det kræver den syntax jo at sproget bliver i stand til at caste et vilkårligt interface med en metode i til en closure - eller er det omvendt?
Funktionelt har de to ting så meget til fælles at man i mange tilfælde (altid?) burde kunne få en compiler til at konvertere mellem dem eller måske ligefrem generere samme kode for dem.
Bare lige en kort bemærkning fra 'the ol' man'. Hvor f.... fokuserer man stadig (20-30 år efter) på at minimere kode.
Intet kan være mere ligegyldigt.
Det vigtige er ikke at gøredet nemt for kodeaben, men at gøre det nemt for brugeren.
ROI gøres op i primært brugerglæde, og dermed bedre arbejdsvilkår, og ikke om en bitflækker kan spare 1 eller 2 kodelinier.
Billigt at lave, dyrt at bruge - think about IT!
Vi bruger i forvejen closures i Java i form af anonyme indre klasser som i det ovenstående. Det er bare ret træls at gøre.
...og det ER vigtigt at gøre det nemt for kodeaben at lave et produkt, som brugerne er tilfredse med, og som kan vedligeholdes på en fornuftig måde.
Bare lige en kort bemærkning fra 'the ol' man'.
Hvor f.... fokuserer man stadig (20-30 år efter) på at minimere kode.
Der er een overvejende god grund til det: Færre kodelinier giver i sidste ende færre fejl. Nuvel, man kan sagtens få for meget af det gode, som du selv er inde på - men lige præcis closures er generelt en rar ting at have. De kan eliminere en stor del boiler-plate kode, gøre koden mere læselig og åbner op for nogle nye måder at beskrive problemer på.
Jeg ser det som en oplagt mulighed for at kunne bruge mindre tid på at arbejde med koden, så man kan bruge mere tid på at arbejde med brugerfladen, eller tilføje mere brugerhjælpende funktionalitet.
Det ligner lidt den nye closure syntax for Objective C (eller rettere Clang).
NSArray *upper = [arr map:^(id obj) { return (id)[obj uppercaseString]; }];
Yikes! Det er da altid noget, at det er en midlertidig syntax fra Javas side.
De closures som diskuteres for Java vil nemlig have samme begrænsning mht. brug af variable fra lexical scope som anonyme indre klasser har: De kan kun referere final variable.
Jeg kan godt se at det giver lidt mere skrivearbejde at de kun kan referere final variable, men er det et problem i praksis?
Jeg kan godt se at det giver lidt mere skrivearbejde at de kun kan referere final variable, men er det et problem i praksis?
Om det bliver oplevet som et problem i praksis vil i høj grad afhænge af indenfor hvilket domæne udvikleren vil anvende disse semi-closures. Det er i høj grad et problem ifht. mange af de områder hvor closures anvendes i andre sprog, f.eks. event-handlers som direkte kan manipulere felter fra samme klasse. Allerede nu har mange javaudviklere vænnet sig til hacks som at placere værdier som skal kunne mutere i enkelt-positions arrays.
Det skal dog nævnes at sprogdesignerne pønser på (iflg. lambda-dev) automatisk at definere de variable/parametre som benyttes af en closure som [i]final[/i] - og give compilerfejl når de bruges på en måde som ikke er kompatibel med "final" (dvs. når værdien forsøges ændret).
Men det primære mål med closures i Java7 er at understøtte parallel programmering. Her er closures [i]meget[/i] anvendelige, men samtidigt er det en ønskværdig egenskab at så mange udtryk som muligt er ikke-muterende. Mit gæt er at det vil føles som en mindre begrænsning indenfor dette område.
Det er på tide, at Java får closure. Så er syntaksen mindre vigtig. Jeg synes ikke, at de viste eksempler ser håbløse ud, men hvis der ikke er en fornuftig notation for polymorfe (generiske) funktionstyper, så kan erklæringer af højereordens metoder/funktioner blive ret stygge at se på (og skrive).
Closures fandtes i øvrigt helt tilbage i Algol 60, hvor de hed "thunks". Thunks svarer nogenlunde til parameterløse funktioner, men da man sammen med en thunk kunne overføre referencer til variable, der indgår i thunkens kode (med det såkaldte "Jensens device", se http://en.wikipedia.org/wiki/Jensen%27s_Device), kunne man nemt implementere funktioner med parametre. Notationen var dog ret styg.