Scala

8 December 2008 17:33 Remco Bos Algemeen

Wat is Scala?
Java is al lang niet meer de enige taal die draait op de Java Virtual Machine. Ruby draait ondertussen ook prima op de JVM net als andere dynamische script talen als Groovy, Clojure en Ioke. Ook Scala draait op de JVM. Maar Scala is net iets anders. Scala is een statisch getypeerde programmeertaal die zowel object-georiënteerd als functioneel is. Scala compileert naar Java bytecode en is daardoor interoperabel met Java. Scala wordt door sommigen wel eens aangeduid als de “Next Java”. Maar ik denk niet dat Scala Java op korte termijn zal vervangen. Maar mocht je toch nieuwsgierig zijn hoe Java er straks uit zou kunnen gaan zien dan kan je vandaag Scala proberen!

Een introductie
Het is onmogelijk om nu alle features van Scala te bespreken. Een aantal goede Scala introducties zijn First Steps to Scala en de The busy Java developer’s guide to Scala. Voor een uitgebreide introductie is er nu de papieren versie van het Programming Scala boek, deze zal zeer binnenkort in de winkel en in mijn boekenkast liggen!
De complexiteit van Scala wordt vaak als een nadeel genoemd. En na het lezen van dit Scala boek vind ik Scala nog steeds complex. Maar ik kan het iedere programmeur aanraden, ik ben er in ieder geval heel wat wijzer van geworden.
Ik wil een aantal voorbeelden geven en aan de hand hiervan het een en ander toelichten. Al deze voorbeelden werken met Scala 2.7.2.

HelloWorld

object HelloWorld {
  def main(args: Array[String]): Unit {
    println("Hello, world!")
  }
}

  • Een object is een soort van “singleton”. Een object wordt gecreeerd op het moment dat deze voor het eerst gebruikt wordt.
  • Een methode definitie begint met een “def”.
  • Een methode is public tenzij anders aangegeven.
  • Scala kent geen statics. Maar een methode in een (singleton) object zou je kunnen beschouwen als een “static” methode in Java.
  • De “;” is optioneel. In veel gevallen kan je deze gewoon weglaten.
  • Een Array is in Scala een Object, dus geen speciale taal constructie.
  • Generics worden in vierkante haken weergegeven.
  • Een return type staat achter de parameter definitie in plaats van ervoor. Daartussen wordt een “:” geplaatst.
  • Het return type Unit staat gelijk aan Java’s void. Het had hier weggelaten kunnen worden.

Een servlet

import javax.servlet.http.{HttpServlet, HttpServletRequest , HttpServletResponse}

class HelloWorldServlet extends HttpServlet {
  val helloWorldPage = <html><head><title>Hello, world!</title></head><body><h1>Hello, world!</h1></body></html>

  override def doGet(req: HttpServletRequest, res: HttpServletResponse) {
    val out = res.getWriter
    out.println(helloWorldPage)
    out.close()
  }
}

  • Scala kent een vereenvoudigde import.
  • HttpServlet is een java class. En ik kan er gewoon van overerven.
  • Scala ondersteunt XML literals!
  • Een “val” is een immutable value (lees final). Een “var” is een mutable value.
  • Override is in Scala een keyword (in plaats van een annotation) en is verplicht bij een override!.
  • Je hoeft geen exceptions te definieren bij een method.
  • Het gebruik van haakjes () is niet altijd verplicht.

Scala is statisch getypeerd
Scala is net als Java een volledig statisch getypeerde programmeertaal. Een type system heeft onder andere dit als voordeel dat bepaalde runtime fouten nooit voor zullen komen. Een Java compiler of Scala compiler zal bijvoorbeeld nooit toestaan dat je een String “123� deelt door een Integer 3. Sterker nog, een IDE geeft al een foutmelding voordat je klaar bent met typen. Ook is een IDE veel beter in staat om voor een statisch getypeerde taal code aan te vullen (code completion) en suggesties te geven. Verder is het met een IDE supereenvoudig om code te refactoren. Een statisch getypeerde taal heeft vaak als nadeel dat er meer code voor nodig is om een bepaald idee te realiseren, maar dit blijkt in Scala heel erg mee te vallen.

Scala is object georiënteerd
Scala is een object georiënteerde taal. Scala kent classes en objects. Alles is een object. Een functie is een object, een value is een object. Scala kent traits en mixin composition. Een trait is een soort van interface. Maar een traits kan naast abstracte members ook members met een implementatie hebben. Met behulp van mixin composition kunnen de members van deze trait in een class toegevoegd worden. Wat ook bijzonder is is dat je values kan definieëren in een trait.

trait DataSourceProvider {
  val dataSource: java.sql.DataSource
}


Een voorbeeld van een trait met implementatie

import org.h2.jdbcx.JdbcDataSource

trait H2DataSourceProvider extends DataSourceProvider {
  val dataSource = {
    val ds: JdbcDataSource = new JdbcDataSource()
    ds.setURL("jdbc:h2:mem:jdbctest;DB_CLOSE_DELAY=-1")
    ds.setUser("sa")
    ds.setPassword("")
    ds
  }
}


Deze kan dan in een class of object gebruikt worden:

object JdbcApp extends DataSourceProvider with H2DataSourceProvider {}


Scala is een functionele taal
Scala heeft functionele eigenschappen zoals bijvoorbeeld closures, higher order functions en currying. Het gebruik van functionele constructies en immutable variabelen wordt sterk aangemoedigd. Het algemene geloof is dat functionele programmeertalen straks gaan excelleren in multi-core omgevingen. Dus functioneel leren programmeren is nu erg nuttig, en levert soms erg mooie code op. Maar Scala is zeker niet een pure functionele taal als bijvoorbeeld Haskell, maar juist de combinatie object georiënteerd en functioneel is een bijzondere die in weinig andere talen voorkomt.

Hieronder een voorbeeld van een higher order function. Dit is een functie die als parameter meegegeven wordt aan een andere functie (een method is ook een soort van functie). De methode exractData verwacht een mapper functie de een rij uit een ResultSet omzet in een generiek type T.

  def extractData[T](rs: ResultSet, mapper: ResultSet => T): List[T] = {
    val results = new ListBuffer[T]()
    var rowNum: Int = 0
    while (rs.next()) {
      results += mapper(rs)
      rowNum += 1
    }
    results.toList
  }

Ik ben eens begonnen met het schrijven van een soort van “JDBC Template�. Een gedeelte van de code is hieronder te zien. Veel regels code is het niet, maar het doet al een behoorlijk portie werk. Het bestaat uit drie gedeeltes, een JdbcApp (de test applicatie), een DataSourceProvider (configuratie van de data source), en een Jdbc trait (de "template").

package jdbc

import javax.sql.DataSource

object JdbcApp extends Jdbc with H2DataSourceProvider {
  def main(args: Array[String]) {
    // Test withConnection
    withConnection { conn =>
      val autoCommit = conn.getAutoCommit
      val catalog = conn.getCatalog
      println("autoCommit: " + autoCommit +  ", catalog: " + conn.getCatalog )
    }

    // Create table
    execute("CREATE TABLE user (email varchar(64), password varchar(64))")

    // Insert row
    update("insert into user(email, password) values(?,?)", "Remco", "geheim")
    update("insert into user(email, password) values(?,?)", "Max", "rottweilertje")

    // User class
    case class User(email: String, password: String)

    // Query
    val userList = query("select email, password from user"){ rs =>
      User(rs.getString("email"), rs.getString("password"))
    }
    userList.foreach(println)
  }
}

import java.sql.{Connection, Statement, PreparedStatement, ResultSet, SQLException}
import javax.sql.{DataSource}
import scala.collection.mutable.ListBuffer

trait Jdbc {
  val dataSource: DataSource

  def withConnection[T](f: Connection => T): T = {
    val conn = dataSource.getConnection
    try { f(conn) }
    finally { conn.close() }
  }
  /** Statement */
  def withStatement(conn: Connection)(f: Statement => Unit) = {
    val statement = conn.createStatement
    try { f(statement) }
    finally { statement.close }
  }

  /** PreparedStatement */
  def withPreparedStatement[T](conn: Connection)(sql: String)(f: PreparedStatement => T): T = {
    val preparedStatement = conn.prepareStatement(sql)
    try { f(preparedStatement) }
    finally { preparedStatement.close }
  }

  /** SQL Operations */
  def execute(sql: String): Boolean = {
    withConnection { conn =>
      val statementCallback = (ps: PreparedStatement) => ps.execute
      withPreparedStatement(conn)(sql)(statementCallback)
    }
  }

  def update(sql: String, args: Any*): Int = {
    val updateCallback = (ps: PreparedStatement) => {
      setValues(ps, args.toArray)
      ps.executeUpdate
    }
    withConnection { conn =>
      withPreparedStatement(conn)(sql)(updateCallback)
    }
  }

  def queryForObject[T](sql: String, args: Any*)(mapper: ResultSet => T): Option[T] = {
    query(sql, args: _*)(mapper) match {
      case x :: Nil => Some(x)
      case x :: xs => Some(x) // throw new IncorrectResultSetColumnCountException
      case Nil => None
    }
  }

  def query[T](sql: String, args: Any*)(mapper: ResultSet => T): List[T] = {
    val queryCallBack = (ps: PreparedStatement) => {
      setValues(ps, args.toArray)
      val rs :ResultSet = ps.executeQuery()
      extractData(rs, mapper)
    }
    withConnection { conn =>
      withPreparedStatement(conn)(sql)(queryCallBack)
    }
  }

  /* Helper methods */
  def setValues(ps: PreparedStatement, args: Array[Any]) {
    for ((arg, i) <- args.zipWithIndex) {
      val index = i + 1
      arg match {
        case b: Boolean => ps.setBoolean(index, b)
        case b: Byte => ps.setByte(index, b)
        case i: Int => ps.setInt(index, i)
        case l: Long => ps.setLong(index, l)
        case f: Float => ps.setFloat(index, f)
        case d: Double => ps.setDouble(index, d)
        case s: String => ps.setString(index, s)
        case d: java.sql.Date => ps.setDate(index, d)
        case _ => ps.setObject(index, arg)
      }
    }
  }

  def extractData[T](rs: ResultSet, mapper: ResultSet => T): List[T] = {
    val results = new ListBuffer[T]()
    var rowNum: Int = 0
    while (rs.next()) {
      results += mapper(rs)
      rowNum += 1
    }
    results.toList
  }
}

import org.h2.jdbcx.JdbcDataSource

trait H2DataSourceProvider {
  val dataSource = {
    val ds: JdbcDataSource = new JdbcDataSource()
    ds.setURL("jdbc:h2:mem:jdbctest;DB_CLOSE_DELAY=-1")
    ds.setUser("sa")
    ds.setPassword("")
    ds
  }
}


Je kan deze code hier downloaden om er zelf mee te experimenteren. Hiervoor is ten minste Java5 en Maven2 (versie 2.0.9 of hoger) nodig. Pak het zip bestand uit en voer het volgende commando uit:

mvn scala:run

Als Maven klaar is met plugins en depencies downloaden zal de uitvoer in de log getoond worden:

[INFO] launcher 'jdbc_app' selected => jdbc.JdbcApp
[INFO] autoCommit: true, catalog: JDBCTEST
[INFO] User(Remco,geheim)
[INFO] User(Max,rottweilertje)

6 reacties »

  1. Ziet er strak uit! Als Ruby developer komen een hoop zaken heel bekend voor. Er zijn nog een paar dingen die mij opvallen:

    * Je gebruikt nog redelijk veel while en for loops. Is daar niet een mooie closure oplossing voor, zoals each?
    * Je introduceert een “generiek type T”. Hoe moet ik dat zien? Als “DIM foo AS VARIANT” in VB?
    * Arrays zijn objecten in scala, maar hoe zit het met de andere “primitives” uit Java? Zijn die ook eindelijk om naar echte objecten?
    * Kan je een voorbeeld geven van de complexiteit van Scala, wat een nadeel zou zijn?
    * Hoe groot is de vrijheid tot metaprogrammeren? Kan ik dynamisch mixins aan classen assignen bijvoorbeeld?

    Iain Hecker December 8, 2008 20:23

  2. * Je gebruikt nog redelijk veel while en for loops. Is daar niet een mooie closure oplossing voor, zoals each?
    Uiteraard is er een “foreach” methode, namelijk in scala.Iterable. Alle Scala collection classes (en wrappers voor Java collection classes) zijn Iterable.

    Maar in bovenstaand voorbeeld wil ik itereren over een java.sql.ResultSet, en dat is het makkelijkst met een ouderwetse while loop (imperatieve stijl).
    Het is wellicht mogelijk om hier een wrapper voor te schrijven maar dat maakt de code nu niet eenvoudiger.

    De for loop is een heel ander verhaal. Dit is geen simpele “loop”. Binnen Scala spreekt men niet over een loop maar over “for comprehensions”. Deze zijn een stuk krachtiger dan zo op het eerste gezicht lijkt. Ik kan je er veel meer van laten zien, maar kijk eens op http://www.scala-lang.org/node/111.

    * Je introduceert een “generiek type T�. Hoe moet ik dat zien? Als “DIM foo AS VARIANT� in VB?
    Nee, je kan in Scala classes parameteriseren met een type (want het is een statisch getypeerde taal). Als ik een een array van type Person wil aanmaken, dan doe ik: val personList = Array[Person]
    Waarschijnlijk is er dan ook ergens een class Array[T]. T staat voor het type.

    * Arrays zijn objecten in scala, maar hoe zit het met de andere “primitives� uit Java? Zijn die ook eindelijk om naar echte objecten?
    Ja, primitives zijn heel echte objecten! 1 is een object. Wat ook wel grappig is: Scala kent geen operators.
    1 + 1 wordt vertaald naar 1.+(1)
    Dus + is een methode op het object 1 en heeft als parameter een Int object. Omdat primitives een stuk sneller zijn dan objecten worden deze waar mogelijk in java bytecode wel weer vertaald naar primitieven. Maar daar merk je als programmeur niets van en conceptueel is alles een object!

    * Kan je een voorbeeld geven van de complexiteit van Scala, wat een nadeel zou zijn?
    Het enige nadeel is denk ik de leercurve. Want hoewel de taal complex is, vind ik deze wel erg netjes en heb ik het gevoel dat er veel beter code in geschreven kan worden.

    * Hoe groot is de vrijheid tot metaprogrammeren? Kan ik dynamisch mixins aan classen assignen bijvoorbeeld?
    Het is geen dynamische taal, metaprogrammeren is lastig. Maar er zijn wel interessante concepten als virtuele superclasses en implicits. Maar dat is een lang verhaal..

    Remco December 12, 2008 0:29

  3. Eigenlijk zou je Scala natuurlijk zo functioneel mogelijk moeten gebruiken om zoveel mogelijk parallelle verwerking mogelijk te maken op multi-processor systemen (bijvoorbeeld Sun’s Niagara ;-) ). In principe vereist functioneel programmeren zoveel mogelijk immutable code en is het dus ook onmogelijk om puur functioneel bezig te zijn (je kunt dan namelijk geen userinterface tonen of data opslaan). Daarom worden er concessies gedaan om het praktisch inzetbaar te maken. Een relationele database is daarom iets wat je eigenlijk niet moet willen in een functionele programmeertaal volgens mij, omdat het echt heel veel kunstjes vereist om het een beetje clean in je functionele code op te nemen. Sowieso zijn relationele databases lastige gevallen wanneer het op concurrency op multi-processor omgevingen aankomt… Ik denk dan ook dat – mochten functionele toestanden doorbreken – er ook een (nog) grotere behoefte komt aan alternatieven voor persistente opslag… Google heeft BigTable en ook CouchDB schijnen wel iets makkelijker te zijn in een functionele taal, maar ik weet niet genoeg er van om dat te beoordelen.
    Je voorbeeld is daarom misschien ook niet het aller beste om de kracht van Scala te laten zien…. Maar ik ben er wel echt zeer in geïnteresseert, zeker na het bijwonen van de lezing van Bill Venners op Devoxx. Jammer genoeg was het boek daar uitverkocht :)

    Martin Sturm December 13, 2008 15:14

  4. @Martin
    Scala is niet bedoeld als een pure functionele taal (zoals Haskell of Clojure), het is juist een fusie tussen een object-georiënteerde en functionele taal.
    Ik denk dat een functionele taal juist geschikt is om een relationele database te bevragen. Dit code voorbeeld van mij is hier absoluut geen goed voorbeeld van, maar SQL is toch ook een soort van functionele taal om relationele sets te bevragen?
    Helaas was ik dit jaar niet op Devoxx, het programma zag er goed uit… Ik zal parleys blijven volgen en misschien komt er weer een dvd uit?

    Remco December 15, 2008 11:58

  5. Ik ben ondertussen ook alweer een paar weekjes met scala bezig, maar ik snap een aantal dingen in je code niet zo goed:

    def withConnection[T](f: Connection => T): T = {

    Wat doet die T daar? Op de plekken waar je deze code gebruikt laat je zowel het block als de function niks teruggeven? Volgens mij kan het gewoon zijn:

    def withConnection(f: Connection => Unit) = {

    Ook vraag ik me af of:

    finally { statement.close }

    wel zo’n slim idee is. Alhoewel die SQLException nu niet meer checked is denk ik toch dat je deze call in bovenstaande code altijd silent wilt doen… ik zou denk ik als excepties hier opeten.

    Verder denk ik dat implicits juist een erg prettige handvat geven voor het uitbreiden van bestaande objecten. En dat werkt dan toch ook op runtime? Misschien moet ik daar eens een blogje aan wijden ;)

    Peter Maas December 30, 2008 22:25

  6. Hoi Remco,

    Interessant verhaal. Maakt Finalist al gebruik van Scala in klant projecten?

    Ron June 30, 2009 13:57

Reageer

RSS feed for comments on this post · TrackBack URI