Java har fået en ny konkurrent. Kast funktioner rundt i programmet som var de objekter, lad compileren selv gætte sig til typerne og skift den besværlige XML-parsing ud med first-class XML-support. Det er bare nogle af de features, som Scala udfordrer Java med. Der er bare et men - Scala er ikke nogen konkurrent! For Scala kører på JVM'en i fred og fordragelighed med dine Java-programmer.

De seneste par år har budt på flere eksempler på nye sprog til Java's virtuelle maskine (JVM'en). At lave nye sprog som eksekverer på JVM'en har flere fordele, bl.a.:

  • JVM'en performer og skalerer rigtig godt.
  • JVM'en er en udbredt og veletableret produktionsplatform i mange organisationer og den findes til mange forskellige operativsystemer.
  • Med JVM'en som fælles platform er grundlaget lagt for interoperabilitet med eksisterende systemer.
  • Med alternativer til Java's syntax kan vi tilbyde specialiserede sprog (DSL'er) til eksempelvis superbrugere af vores systemer - uden at skulle lære dem Java.
  • Med alternativer til Java's syntax kan vi få adgang til sproglige features som ellers ikke er til rådighed i Java.

I visse tilfælde har der været tale om re-implementeringer af eksisterende sprog til JVM'en. Eksempelvis er Ruby (JRuby), Python (Jython), JavaScript (Rhino) og PHP (Quercus) alle re-implementeret på JVM'en for at kunne tilbyde overlegen performance og interoperabilitet i et Java EE miljø.

Endnu mere spændende har udviklingen været af helt nye sprog. Nogle af de mest omtalte nye sprog til JVM'en er GroovyScala og Clojure. I denne artikel vil jeg fortælle om dét af de nye sprog som jeg personligt synes ser mest lovende ud, nemlig Scala. Scala's kendetegn er kort fortalt, at det er type-stærkt, understøtter type-inference og operator-overloading, er både objektorienteret (som ex. Java og C#) og funktionelt (som ex. Erlang, Haskell og ML) og skalerer rigtig godt.

scala-logo

Med blandingen af objektorienteret og funktionel programmering kunne man frygte at Scala er sætter sig mellem to stole, men det er mit indtryk at der er fundet en fornuftig hybrid. Det er vores klare indtryk at tiden er moden til at få en række funktionelle aspekter ind i vores velkendte produktionssprog. Her er Scala et ambitiøst og lovende projekt, der kan gøre koden mere fleksibel, overskuelig og fejltolerant.

Der er mange spændende nyheder i Scala og jeg har i denne artikel ikke mulighed for at berøre dem alle. Jeg har udvalgt tre emner som jeg synes er særligt vigtige - og som gør Scala til et meget interessant alternativ til vores gamle ven Java:

  • Funktioner
  • Immutability
  • Sproglig support for XML

Funktioner og objekter i skøn forening

Scala er et funktionelt sprog. Det er samtidigt et objektorienteret sprog. Men da jeg antager at du allerede kender Java (og dermed ved hvad objektorientering er) vil jeg slå ned på det funktionelle. Tanken med funktioner er, at man som en del af sproget kan referere til en funktion på samme måde som vi kan referere til et objekt. Når man kan referere til funktioner kan man også bedre bestemme hvornår funktionen kaldes og dermed kan man designe API'er med langt bedre indkapsling og hvor kontrol-logik ikke er synlig for brugeren.
I Scala ser type-erklæringen af en funktion, der returnerer en String således ud: " => String". Eksempelvis kan man definere at en parameter til en metode er en funktion. Overvej følgende eksempel:

1: def log(message : => String) {
2:   if (loggingEnabled) {
3:     println(message)
4:   }
5: }

og et eksempel på kaldende kode:

log("Something important happened: " + importantObj.getStatus())

Bemærk om eksemplet følgende:

  • Parametren "message" er en funktion. Den evalueres ikke med det samme, men først på linie 3 ("println(message)").
  • If-sætningen ("if (loggingEnabled) { ... }") repræsenterer kontrollogik som er indkapslet således at den kaldende del ikke skal "forurenes" med kontrollogik som man måske overser i kampens hede.
  • Log-beskeden som konkateneres i den kaldende kode bliver ikke evalueret med det samme. Det skyldes, at message-parametren er en funktion. Hvis loggingEnabled er false, så vil konkateneringen aldrig finde sted! Dette kan være ganske væsentligt - Det kan jo være at getStatus()-metoden på importantObj ikke er et trivielt metodekald.

I virkeligheden har vi i forvejen noget der ligner funktioner utroligt meget i Java, nemlig anonyme indre klasser, men de er bare bøvlede at bruge i flæng fordi de syntaktisk kræver en hel del. Desuden kræver de ofte også, at man skal lave et nyt interface, som de anonyme indre klasser skal overholde (i ovenstående eksempel var "interfacet" blot "=> String"). Disse uhensigtsmæssigheder betyder, at man i Java meget sjældent benytter funktioner på samme måde som man gør i funktionelle sprog. Det er uheldigt, da man med funktioner kan lave meget elegant kode. I stedet får vi typisk en kaldende kode i Java, der ser således ud:

if (loggingEnabled) {
  log("Something important happened: " + importantObj.getStatus())
}

Kravet om at tjekke loggingEnabled-variablen er mao. på den kaldende side. Antag nu, at kontrollogikken er mere kompliceret - så vælter læsset! Det er selvfølgelig også muligt at indkapsle kontrollogikken i Java, men så er der stor sandsynlighed for at ovenstående log(...)-metode vil blive benyttet uhensigtsmæssigt meget.

Konsekvensen ved det funktionsorienterede i Scala er, at API'erne i Scala er indrettet meget mere brugervenligt. Hvis du eks. vil iterere igennem en collection skal du ikke selv definere løkkerne (hvor mange gange har du ikke lavet en fejl i en for-løkke?), men blot kalde en foreach(...) metode med en funktion der håndterer hvert element. Dette giver desuden muligheden for at indkapsle anden logik ifbm. iterering, ex. hvis foreach(...)-metoden indkapsler opslag mod databasen, auditing eller andre ting der bør være usynlige for den kaldende kode. Tilsvarende er Scala's API fyldt med metoder hvor du spares for "det hårde arbejde". Lad os eksempelvis sige, at du vil fordele elementerne i en liste af personer efter alder og køn, så kan du benytte den indbyggede partition metode:

val (kids, adults) = persons.partition(_.age < 18)
val (maleAdults,femaleAdults) = adults.partition(_.gender == 'male')

Bemærk i eksemplet:

  • Vi benytter her en meget kort notation for at sende en funktion med som parameter til metoden partition. Udtrykket "_.age < 18" er rent faktisk en funktion, hvor underscore (_) repræsenterer en parameter til denne funktion. Underscore er i Scala et særligt tegn som bruges i mange situationer, eks. som her til at referere en unavngiven parameter.
  • En metode kan have multiple retur-typer (omringet af parenteser, adskildt med komma) - partition-metoden returnerer to lister.
  • Det kunne godt se ud som om at sproget er typesvagt eller dynamisk (da "val" ikke er nogen type-erklaring), men det er ikke tilfældet. Typen er blot "inferred", dvs. bestemt af retur-værdien defineret af partition-metoden.

Immutability - don't mess with my state

At et objekt er immutable betyder, at man ikke kan ændre dets tilstand. Man kan typisk lave en kopi med en modificeret tilstand, men original-objektet vil være upåvirket. Dette princip kendes bl.a. fra Javas String's som er immutables; man kan ikke ændre tilstanden af et String-objekt, men producere et nyt med en ændret tilstand, fx med kaldet "java".toUpper(). At lave immutable objekter har en række fordele:

  • Hvis dine objekter er immutable, vil dine metoder/funktioner altid være "side-effect free" og udvikleren skal altså ikke bekymre sig om at påvirke tilstanden i et samtidigt miljø.
  • Når tilstanden ikke ændres betyder det også, at der ikke er brug for låsning ifbm. samtidige kald. Dette skaber altså mærkbare performance fordele.
  • Det er typisk meget lettere at følge ændringerne i ens objekter. Hvis du f.eks. ændrer navnet på et Person-objekt vil dette returnere en ny person. Således har du både den nye og den oprindelige tilstand (hvis du da stadig refererer til den oprindelige)

Scala opfordrer udvikleren til at lave sine klasser immutable ved at skelne meget præcist mellem hvad der er mutable og immutable. Variable med disse karakteristika erklæres nemlig med hvert sit keyword - val (immutable) og var (mutable):

case class Person(val name: String)

val p1 = Person("Martin Odersky")
val p2 = Person("James Gosling")
val languageAuthors = List(p1 , p2)
var i = 0
languageAuthors.foreach({
  i = i+1
  println(_.name)
})
println("I have just iterated through " + i + " language authors")

Bemærk i eksemplet, at kun "i"-variablen er mutable. Vi har effektivt sikret de andre variable mod at blive modificeret så vi skal ikke bekymre os om samtidighed eller andre issues. Det væsentlige her er selvfølgelig, at vi med ro i sindet kan sende både p1, p2 og languageAuthors videre til andre metode-kald uden at frygte for om tilstanden ændes af denne grund.

Den store aha-oplevels ifht. immutability kom til mig, da jeg arbejdede med Scala's lister. I Scala er List immutable. Det betyder, at når man tilføjer eller fjerner elementer på en liste, så får man i virkeligheden en helt ny liste. Tilsvarende hvis man vil filtrere elementer ud eller lign., så benytter man de indbyggede filter(...), map(...) mfl. metoder der er på List-klassen, som også returnerer nye lister. Dette betyder bl.a. at man undgår famøse exceptions som ConcurrentModificationException og IndexOutOfBoundsException.

Bag overfladen er List i Scala implementeret som en linked list, hvor links i høj grad kan genbruges på tværs af List-instanser, så performance-mæssigt er det ingen hindring.

Det betyder også, at man i Scala ikke er udsat for de lettere kuriøse regler i Java om hvornår en liste er en subtype af en mere generel liste (eksempelvis gælder det i Java, at List<String> IKKE er subtype afList<Object>). Hvorfor? Fordi man i Scala ikke skal bekymre sig om use-casen "hvad så hvis vi tilføjer et element til listen, som ikke er af subtypens type - så er listens typeparameter kompromiteret". I Scala gælder det altså, at List[String] er en subtype af List[Object] og så fremdeles.

XML - nu en del af sproget!

Scala har indbygget parsing af XML. Det betyder, at man kan erklære en variabel baseret på en XML-struktur direkte uden brug af String-literaler eller lign.:

val languageAuthorsNode = <persons>
  <person><name>Martin Odersky</name></person>
  <person><name>James Gosling</name></person>
</persons>

Bemærk her fraværet af nogen form for String-literaler - XML-noderne er indtastet direkte i koden og parses af compileren.

Hvad kan dette så bruges til? Mange ting faktisk. XML er en stor del af de fleste udvikleres hverdag og med Scala kan du meget lettere servere dine data på XML-form uden at skulle igennem komplicerede API'er til traversering, transformering osv. osv. Antag eksempelvis at vi ville konstruere ovenstående node, men på baggrund af vores liste beskrevet tidligere. Så ville vi kunne lave en metode som følger:

def generateNode(persons: List[Person]) = <persons>
  { persons.map( p => <person><name>{p.name}</name></person> ) }
</persons>

Denne mekanisme bliver bl.a. benyttet af det Scala-baserede webframework Lift til at lave custom tags til HTML på en enormt simpel måde (et tag implementeres altså bare ved at lave en metode der returnerer en XML/XHTML node).

Konklusion

Det er altid meget svært at spå om hvilke sprog der bliver succesfulde og hvilke der bliver overset i mængden, så det vil jeg ikke prøve på. Jeg kan kun sige, at med mange år på bagen som Java-udvikler synes jeg at det har været en fornøjelse at stifte bekendtskab med Scala. Der er i Scala som i alle andre sprog naturligvis lidt af en indlæringskurve, men heldigvis behøver man ikke favne over det hele fra start. Der er mange andre spændende features i Scala som jeg ikke er kommet ind på i denne artikel, bl.a. Actor-baseret samtidighed, State-pattern-matching, Traits, Implicit conversion og meget mere. Måske vender jeg tilbage til disse emner i en anden artikel, hvis der er interesse for det. Dette bringer mig til sidste budskab: Hvis du synes Scala (eller et andet emne) bare lyder møg-interessant at blive klogere på, så log ind på hjemmesiden og markér din interesse.

Læs mere om Scala

About the Author -