Het is alweer twee jaar geleden dat Scala aan bod kwam in het Java Magazine. Scala is inmiddels een volwassen taal geworden en krijgt steeds meer aanhangers. Logisch, want Scala biedt nu al features die op z’n vroegst pas over twee jaar in Java zijn te verwachten. Dit resulteert niet alleen minder code, maar levert ook betere en beter leesbare code op. Veel Java projecten zijn echter groot en al jaren oud. Het is dus niet reëel om te verwachten dat zo’n project opnieuw in Scala gedaan zal worden. Dat is ook niet nodig, want Scala is zeer goed in te zetten in bestaande projecten.
De evolutie van Java staat al jaren op een laag pitje. Talen zoals JRuby, Groovy en Scala vullen gretig het gat dat Java heeft laten vallen, door nu al features te bieden die nog steeds alleen maar op de wishlist van Java staan. Net als de andere talen heeft ook Scala de afgelopen jaren veel nieuwe aanhangers gewonnen. Met features als geavanceerde pattern matching, multiple inheritance door middel van traits, functioneel programmeren, type parameterization en impliciet conversions ligt Scala jaren voor op Java. Concurrency is in Scala veel eenvoudiger dan in Java, waardoor alle cores van een systeem beter gebruikt kunnen worden. Hierdoor is de performance van Scala soms zelfs beter dan die van Java. Voor niet-concurrent code is de Scala performance ongeveer gelijkwaardig. Al deze featues leveren niet alleen beter leesbare code, maar ook minstens 50% minder code op. En minder code betekent ook minder bugs. De meesten van ons werken echter op projecten waarin al jarenlang Java wordt gebruikt en lang niet iedereen heeft de mogelijkheid om een project opnieuw te beginnen met Scala. Scala runt op dezelfde VM als Java, dus zouden we de talen door elkaar moeten kunnen gebruiken, maar hoe compatible is Scala eigenlijk en hoe combineren we Java en Scala binnen één project?
Scala is volledige compatible met Java. Java en Scala kunnen in een project door elkaar gebruikt worden zonder dat hierbij cyclische problemen onstaan. Scala code kan dus afhankelijk zijn van Java code en andersom, omdat de Scala compiler scalac de Java code kan parsen en analyseren. Scalac is geen volledige Java compiler, maar het produceert een Abstract Syntax Tree (AST) voor elke Java source file, waardoor de Scala classes gecompileerd kunnen worden. De output kan gebruikt worden door de Java compiler om alle Java sources te compileren. Dit hoef je allemaal niet zelf te doen. De IDE’s en tools als Maven en SBT zorgen dat dit voor je gedaan wordt.
Scala kan alle Java code gebruiken. Een Scala class kan Java classes extenden, interfaces implementeren en Scala kan overweg met alle Java types. Daar waar in Java primitive values gebruikt worden, kan in Scala gewoon met value type objecten, Int, Long, etc, gewerkt worden. Dit kan omdat de Scala compiler de value type bijna altijd vertaalt naar Java primitives voor betere performance. Een van de weinige uitzonderingen hierop is wanneer value types gebruikt worden in een collectie. Bij gebruik van Java methodes in Scala moet wel opgelet worden dat de naam niet conflicteert met een Scala keyword. Dit resulteert soms in een onverklaarbare foutmelding. Voorbeelden hiervan zijn bijvoorbeeld java.util.Scanner.match() of Matchers.eq(), welke conflicteren met de match en eq keywords van Scala. Om dit probleem te omzeilen kan de methodenaam tussen back quotes gezet worden. Voor static methodes kan de classnaam ervoor gezet worden of de methode hernoemd worden via een import.
Scanner scanner = new Scanner(new File("somefile"));
// Error: identifier expected but 'match' found.
scanner.match();
// Ok
scanner.`match`()
val serviceMock: ScalaServiceImpl = mock(classOf[SomeService])
// Error: type mismatch
when(serviceMock.someMethod(eq("Arg"))).thenReturn("Some result")
// Ok: class voor methode
when(serviceMock.someMethod(Matchers.eq("Arg"))).thenReturn("Some result")
// rename 'eq' method to 'meq'
import org.mockito.Matchers.{eq => meq}
// Ok: methode hernoemt naar 'meq' via via import
when(serviceMock.someMethod(meq("Arg"))).thenReturn("Some result")
Codevoorbeeld 1: methodenaam conflicten
In Java kunnen uiteraard ook Scala classes gebruikt worden. Dit gaat echter niet altijd zomaar op. Scala compileert wel naar compatible bytecode voor de JVM, maar sommige features zoals traits vertalen niet direct naar code die in Java gebruikt kan worden. Het is dus handig om wat meer kennis te hebben over de interoperabiliteit tussen Java en Scala om problemen te voorkomen.
Scala kent geen interfaces zoals in Java, maar traits. Traits kunnen behalve methode definities ook methode implementaties en velden bevatten. Alleen als een trait geen implementatie of velden bevat, kan deze als interface in Java gebruikt worden. Het is echter veiliger om Java interfaces in Scala te gebruiken dan andersom. Zo voorkom je problemen met Java implementaties wanneer er in de toekomst een implementatie of veld aan de trait toegevoegd zou worden. Een Scala class kan een interface implementeren door met het with keyword één of meerdere interfaces toe te voegen. Er is één uitzondering: indien er nog geen extends keyword is gebruikt, dan moet voor de eerste interface extends worden gebruikt.
class MultipleInterfacesImpl extends Comparable[Int] with Cloneable {
def compareTo(p1: Int) = 0
}
Codevoorbeeld 2: Implementeren van meerdere interfaces in Scala.
Waar in Java gekunsteld moet worden met public static final instances of enum’s om een singleton te maken, is dit een stuk eenvoudiger in Scala omdat singleton een standaard feature is. Een Scala singleton kan Java worden gebruikt alsof het in Java was geschreven.
Voor de vertaling naar Java maakt de Scala compiler gebruik van een combinatie van static en instance methodes. Voor een singleton met de naam ‘App’ maakt scalac een Java class met de naam App$. De App$ class bevat instance methodes voor elke methode van de singleton en een static field MODULE$ welke runtime de enige instance van de class bevat. De compiler maakt ook een Java class zonder de ‘$’ postfix, in dit geval dus App, ook als er geen companion class - een class object met dezelfde naam als het singleton object - is gedefinieerd. De App class krijgt voor elke methode van de singleton een public static methode. Deze methodes sturen de call door naar dezelfde methode van de App$.MODULE$ instance.
Vanuit Java kan een singleton gebruikt worden via de static methodes. De singleton kan daarnaast ook via Singleton$.MODULE$ verkregen worden.
In Singleton.scala:
package scalaservice
object Singleton {
def greet(name: String) = "Hello " + name
}
In SingletonTest.java:
@Test
public void useSingleton() {
assertThat(Singleton.greet("Scala"), is("Hello Scala"));
assertThat(scalaservice.Singleton$.MODULE$.greet("World"), is("Hello World"));
}
Codevoorbeeld 3: Gebruiken van Scala singletons in Java.
In de interactie met Java code is het soms nodig om exceptions af te handelen of exceptions te gooien. Scala kent wel de normale try-catch-finally clausule, maar heeft echter niet zoals Java een throws keyword, omdat Scala alleen runtime exceptions heeft. In plaats daarvan kan in Scala voor elke exceptie een @throws annotatie boven de methodedefinitie geplaatst worden. De compiler vertaalt dit naar een Java ‘throws’ declaratie. Om vanuit Scala een Java methode te gebruiken die een exception gooit, kan Scala’s ‘try-catch’ gebruikt worden.
class JavaServiceImplInScala extends JavaService {
@throws(classOf[IllegalArgumentException])
@throws(classOf[OtherException])
def throwsException: Unit = try {
doSomething
} catch {
case x: RuntimeException => throw new IllegalArgumentException
}
}
Codevoorbeeld 4: try-catch exception en multiple @throws
Implicit conversions is een bijzonder krachtige feature van Scala waarbij types automatisch geconverteerd kunnen worden in andere types. Naast de conversies voor de belangrijkste Java types zoals java.lang.String, java.lang.Integer, etc, biedt Scala ook een aantal conversies voor het converteren van de belangrijkste Java collectie types naar Scala types en omgekeerd. Bij zo’n conversie wordt een wrapper object gemaakt dat alle operaties doorstuurt naar het onderliggende object. Bij een conversie worden collecties dus nooit gekopieerd. Een roundtrip conversie van Java naar Scala naar Java levert zelfs dezelfde collectie instance op als waar mee begonnen was. Omgekeerd, van Scala naar Java naar Scala, geldt dat niet altijd. Java kent immers geen immutable collecties zoals Scala. Zowel Scala’s mutable als immutable collecties converteren naar Java collecties, maar Java collecties converteren alleen naar mutable Scala collecties. De wrapper zorgt er wel voor dat een immutable Scala collectie ook immutable is in Java. Als in Java zo’n collectie toch wordt gewijzigd, zal de wrapper een UnsupportedOperationException gooien.
De conversies worden geactiveerd door het importeren van alle methodes van de JavaConversions singleton.
scala> import collection.JavaConversions._
import collection.JavaConversions._
Console 5: Activeren van impicit Java conversies.
Hierna zal Scala automatisch een conversie toepassen waar dat nodig is. In het volgende voorbeeld wordt een scala.collection.immutable.List omgezet in een java.util.List. Bij het terugconverteren naar Scala wordt dit een scala.collection.mutable.Buffer, de mutable variant van een Scala List. Ook Scala maps converteren naar Java maps en omgekeerd.
scala> val list: java.util.List[Int] = List(1,2,3)
list: java.util.List[Int] = [1, 2, 3]
scala> val seq: Seq[Int] = list
seq: scala.collection.mutable.Seq[Int] = Buffer(1, 2, 3)
scala> val m: java.util.Map[Int, String] = Map(1 -> "een", 2 -> "twee")
m: java.util.Map[Int,String] = {1=een, 2=twee}
Console voorbeeld 6: Automatisch converteren van Java naar Scala types en omgekeerd.
Scala kan echter maar één conversie per keer doen. Een conversie van java.lang.Integer naar Int of van java.util.List naar Seq of omgekeerd is mogelijk. Het is bij collecties echter niet mogelijk om ook het element type direct te converteren. Een java.util.List[java.lang.Integer] kan niet omgezet worden in een Buffer[Int]. Het element type moet bij een conversie dus gelijk blijven.
scala> val list: Buffer[Int] = new java.util.ArrayList[java.lang.Integer]()
<console>:13: error: type mismatch;
found : java.util.ArrayList[java.lang.Integer]
required: scala.collection.mutable.Buffer[Int]
val list: Buffer[Int] = new java.util.ArrayList[java.lang.Integer]()
^
scala> val list: Buffer[java.lang.Integer] = new java.util.ArrayList[java.lang.Integer]()
list: scala.collection.mutable.Buffer[java.lang.Integer] = Buffer()
Console voorbeeld 7: Converteren van een collectie.
Zodra de collectie is omgezet in een Scala type, kan ook met de elementen gewerkt alsof het Scala types zijn. De conversie vindt dan plaats bij het toevoegen aan of ophalen van een het element in de collectie.
scala> list += (2,4,99)
res1: list.type = Buffer(2, 4, 99)
scala> list(2)
res2: java.lang.Integer = 99
scala> val i: Int = list(2)
i: Int = 99
Console voorbeeld 8: Gebruiken van element in een collectie.
Voor het gebruik van Java collecties betekent dit dat de Scala collectie exact hetzelfde element type moet hebben, ook al kan het element naar een Scala equivalent geconverteerd worden. Indien in Scala een Java interface wordt geïmplementeerd, moet de functiedefinitie identiek zijn aan die in de Java interface, zowel het Java collectie type als het element type. Pas binnen de functie zelf kunnen conversies worden toegepast.
JavaService.java:
public interface JavaService {
public Map<String, Integer> process(List<Integer> l);
}
JavaServiceImplInScala.scala:
class JavaServiceImplInScala extends JavaService {
def process(list: java.util.List[java.lang.Integer]): java.util.Map[String, java.lang.Integer] = HashMap("een" -> new Integer(list(0) + 1))
}
Codevoorbeeld 9: Implementeren Java interface in Scala met Java collectie types.
In dit voorbeeld wordt in Scala een methode geïmplementeerd welke een Java collectie als argument en als return type heeft. De implicit conversion vindt pas plaats in de methode implementatie, niet in de definitie! De parameter list wordt in een Scala collectie omgezet, zodat we het list(0) het eerste element in de lijst kunnen ophalen. Het element in list wordt op dat moment omgezet in een Int en wordt 1 verhoogd. De nieuwe waarde moet als Integer in de map gezet worden, omdat het element type niet geconverteerd kan worden. HashMap is een Scala collectie die wel geconverteerd wordt in een java.util.Map. Alhoewel in Scala de return type van een functie weggelaten kan worden, is het voor public functies toch aan te bevelen om het return type wel in te vullen, zodat uit de code duidelijk de API van de class blijkt, tenzij dit vanuit de code al duidelijk genoeg is.
Tijdens het compileren vertaalt de Scala compiler value types zoveel mogelijk naar Java primitive types voor een betere performance, maar Scala’s collection classes worden niet omgezet in Java collections. Als in Java code een Scala functie wordt gebruikt welke een Scala collectie type heeft als argument of return type, moet in Java deze Scala collectie gebruikt worden. Dit levert echter twee problemen op. Ten eerste zijn sommige Scala collectie types zoals een List moeilijk in Java te maken. Het heeft de voorkeur om in Java gewoon met de standaard Java collections te werken. Echter, Java kent geen implicit conversions en daarom moet de conversie van collectie types expliciet in de code gedaan worden. Scala’s JavaConverters of JavaConversions classes zijn hiervoor zeer nuttig. Ten tweede, wanneer het element type van een Scala collectie een value type is, vervangt de Scala compiler het type door ‘Object’. Een List[Int] wordt dus een List[Object], wat problemen oplevert indien er een ander type dan een Int in zit. Het volgende voorbeeld toont beide problemen.
ScalaService.scala:
class ScalaServiceImplInScala {
def process(list: List[Int]): Map[String, Int] = Map("een" -> (list(0) + 1))
}
In Java code:
ScalaServiceImplInScala service = new ScalaServiceImplInScala();
List<Object> list = new ArrayList<Object>(Arrays.asList(new String[]{"1"}));
scala.collection.immutable.List<Object> scalaList = (scala.collection.immutable.List<Object>) JavaConversions.asScalaBuffer(list).toList();
scala.collection.immutable.Map<String,Object> map = service.process(scalaList);
Map<String,Object> javaMap = JavaConverters.mapAsJavaMapConverter(map).asJava();
Codevoorbeeld 10: Gebruik van Scala collecties in Java
In dit voorbeeld verwacht de Scala service een List[Int] maar vanuit Java moet een List
Javap is een handige tool in de Java distributie waarmee inzicht verkregen kan worden in zowel Java- als Scala classes. Met name voor Scala classes is het handig om te zien hoe de Scala compiler Scala code naar Java classes vertaalt. Met de ‘-verbose’ optie wordt meer informatie getoond en is het ook mogelijk om onder andere het element type van een collectie te zien. Run de tool in de console in de directory waar de .class staat.
$ javap ScalaServiceImplInScala
Compiled from "ScalaService.scala"
public class scalaservice.ScalaServiceImplInScala extends java.lang.Object implements scalaservice.ScalaService,scala.ScalaObject{
public scala.collection.immutable.Map process(scala.collection.immutable.List);
public scalaservice.ScalaServiceImplInScala();
}
Console voorbeeld 11: output van javap tool.
Javap kan ook gebruikt worden in de Scala interpreter. Door de interpreter te gebruiken via SBT of in de IDE staan alle project classes direct op het classpath van de interpreter, en kan je gebruik maken van de code completion. De volledige class naam inclusief package moet gebruikt worden, ook al is er een import gedaan.
scala> :javap scalaservice.ScalaService
Console voorbeeld 12: Gebruik van javap in Scala interpreter.
Maven is nog steeds een van de meest gebruikte build tools voor Java projecten. Support voor Scala kan eenvoudig worden verkregen door een paar plugins toe te voegen aan een project pom. De maven-scala-plugin zorgt voor het compileren van de Scala code en kan daarnaast ook Scala code of script runnen, continue compileren en testen of een Scala console starten. In de plugin configuration kan de gewenste Scala versie gezet worden. De compile goals worden aan de process-resources fase toegevoegd, zodat de Scala code voor de Java code gecompileerd wordt. De surefire plugin moet geconfigureerd worden, zodat deze niet alleen Java testen maar ook Scala testen runt. Merk op dat voor Scala testen de class files worden toegevoegd en niet de source files. De Scala Tools repository is nodig voor de benodigde Scala libraries en tools.
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<version>2.14.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>2.9.1</scalaVersion>
</configuration>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.11</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.class</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>ScalaToolsMaven2Repository</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases/</url>
<layout>default</layout>
</repository>
</repositories>
Codevoorbeeld 13: Scala support toevoegen aan Maven pom.
SBT staat voor Simple Build Tool. Dit is de tool waar Scala projecten mee worden gebouwd, maar ook voor gecombineerde Java-Scala projecten kan deze tool een handige toevoeging zijn. Ten opzichte van Maven heeft het een aantal voordelen:
De SBT configuratie is gewoon Scala code en ook plugins zijn in Scala code te schrijven zonder dat daar een package voor gemaakt moet worden. Hierdoor is het eenvoudig om de build configuratie te customizen voor elk denkbare configuratie.
Intern heeft SBT een Scala compiler draaien waardoor het incrementeel en sneller code kan compileren.
SBT heeft een handige feature zodat elke commando continue uitgevoerd kan worden. Dit is met name handig voor testen. Door continue het test commando uit te voeren worden de testen uitgevoerd zodra er code gewijzigd en opgeslagen is.
Vanuit SBT kan ook de Scala interpreter console gestart worden. Alle project classes en resources staan dan direct op de classpath van de console, zodat deze eenvoudig in de console gebruikt kunnen worden. SBT bevat nog veel meer features en is uitstekend gedocumenteerd. Meer informatie staat op de site van XSBT.
> sbt ~test
Console voorbeeld 14: Continue uitvoeren van test commando door ‘~’ prefix.
Zowel in IntelliJ als Eclipse is het mogelijk om een SBT console te gebruiken waardoor je de continue testen functie kan gebruiken in de IDE. Waar voor Maven de pom.xml het project beschrijft, doet build.sbt dat voor SBT. De meeste projecten gebruiken echter al Maven en het is niet wenselijk om twee projectconfiguraties bij te moeten houden. Gelukkig is dit niet nodig, want met de externalPom() functie kan SBT project dependencies vanuit een pom inlezen. Door de locale Maven repository aan de dependency resolvers toe te voegen, worden dependencies in ieder geval niet dubbel gedownload van een externe bron. Omdat SBT een Ivy repository gebruikt, worden de jars echter wel dubbel opgeslagen op disk.
Met de volgende drie regels in de build.sbt file, kan SBT gebruikt worden voor een Maven project:
resolvers += "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"
externalPom()
Codevoorbeeld 15: Maven pom gebruiken met SBT
Voor SBT zijn inmiddels ook al veel plugins beschikbaar. Zo zijn er plugins voor Eclipse en IntelliJ voor het genereren van project files. De Eclipse plugin heeft een handige feature die, als deze in een lege directory wordt gestart, automatisch de benodigde project directory structuur aanmaakt. Deze structuur is gelijk aan die van Maven. Standaard heeft SBT support voor de Scala test frameworks. JUnit-interface plugin zorgt ervoor dat ook JUnit testen met SBT gestart kunnen worden. Het toevoegen van de dependency voor JUnit-interface in build.sbt werkt niet in combinatie met externalPom, en dus moet de dependency in de Maven pom worden toegevoegd.
<dependency>
<groupId>com.novocode</groupId>
<artifactId>junit-interface</artifactId>
<version>0.7</version>
<scope>test</scope>
</dependency>
Codevoorbeeld 13: Toevoegen JUnit-interface dependecy in pom.
De adoptatie van een nieuwe taal of techniek staat of valt met goede IDE support. Voor Scala is dat tegenwoordig uitstekend in orde. IntelliJ heeft al lang een goede support voor Scala. De Scala en SBT plugins zijn eenvoudig te installeren via de Plugin Manager. Een project pom waaraan de maven-scala-plugin al is toegevoegd kan eenvoudig als nieuw project geopend worden, waarna alles direct al geconfigureerd is. IntelliJ heeft support voor FSC, de Fast Scala Compiler, en via de settings kan de compile volgorde scalac, javac of javac, scalac ingesteld worden. Een klein nadeel van IntelliJ is dat niet alle features werken voor Scala, zoals het maken van constanten of het toevoegen van een static import.
Sinds eind 2010 werkt Typesafe mee aan de plugin voor Eclipse en is de naam omgedoopt naar Scala-IDE. Afgelopen december is versie 2.0 uit gekomen. Ondanks dat deze versie vooral veel bugfixes bevat en maar weinig nieuwe features, is dit toch al een hele verbetering en is er heel goed mee te werken. Voor versie 2.1 staan veel nieuwe features gepland. Scala-IDE heeft, net als IntelliJ, de benodigde wizards voor het maken van de Scala types en project. Aan bestaande Java projecten kan de Scala nature worden toegevoegd voor Scala support. De code completion feature is ook prima in orde en wordt nu ook gesupport voor test frameworks zoals ScalaTest. Ook met Scala-IDE is de compilatie volgorde in te stellen, maar er is geen FSC support.
In combinatie met Maven moet ook de m2e-scala plugin geïnstalleerd worden. Deze zorgt ervoor dat de maven-scala-plugin geen problemen veroorzaakt en ook dat de Scala source folders en Scala nature direct aan een project wordt toegevoegd als deze als Maven project wordt geïmporteerd.
Voor het runnen van SBT in Eclipse is een een aparte plugin beschikbaar. Nadat deze is geïnstalleerd, moet eerst het SBT nature aan het project worden toegevoegd waarna de console kan worden gestart. Zowel voor IntelliJ als Eclipse is de Scala support uitstekend. Ook voor NetBeans schijnt een prima Scala plugin beschikbaar te zijn. Ongeacht welke IDE je voorkeur heeft, het is prima in te zetten om behalve Java code ook Scala code te ontwikkelen.
Scala is een mooie maar complexe taal. De beste manier om de taal te leren, is door deze in de praktijk toe te passen. Omdat Java en Scala uitstekend gecombineerd kunnen worden, kan Scala prima langzaam in bestaande Java projecten worden geïntroduceerd. Een goed startpunt om met Scala te beginnen zijn testen. Uiteraard is het mogelijk om in Scala JUnit testen te schrijven, maar er zijn ook een aantal goede test frameworks voor Scala. ScalaTest is een uitstekende framework waarmee testen in diverse stijlen te schrijven zijn, van JUnit tot aan BDD. Spec2 is een framework dat zich vooral op BDD testen richt. Met ScalaCheck schrijf je specificaties voor de code waarna automatisch test data voor de specificaties gegenereerd wordt. Alle frameworks kunnen behalve Scala code uiteraard ook Java code testen. De eerste regels Scala code zullen waarschijnlijk nog veel op Java lijken, maar naargelang er meer ervaring en meer kennis van de Scala API opgedaan wordt, zal je zien hoe krachtig de Scala taal is en met hoeveel minder code iets is te realiseren in Scala. Vanzelf zal meer en meer code in Scala geschreven worden. Het uiteindelijke doel moet echter niet zijn om alles in Scala te schrijven, maar voor de te implementeren functionaliteit die taal te gebruiken, die daar het best voor geschikt is.
Joost den Boer
Freelance senior Java/Scala consultant
Resources om snel aan de slag te gaan met Scala:
Referenties:
Gebruikte software versies: