Scala Programming Language Tomáš Bureš DISTRIBUTED SYSTEMS RESEARCH GROUP http://nenya.ms.mff.cuni.cz CHARLES UNIVERSITY PRAGUE Faculty of Mathematics and Physics
What is Scala Programming language class-based imperative with a lot of functional constructs statically typed a bit richer type-system type inferences support for XML compiled to Java-bytecode quite seamless interoperability with Java basically supported in Eclipse, Netbeans and IntelliJ Idea
Some basic notes Primitive types same as in Java
Objects and classes // In file Summer.scala // In file ChecksumAccumulator.scala import ChecksumAccumulator.calculate object Summer { def main(args: Array[String]) { for (arg <- args) println(arg +": " + calculate(arg)) main method singleton objects classes vals & vars type parameterization type inference semicolon inference class ChecksumAccumulator { private var sum = 0 def add(b: Byte) { sum += b def checksum(): Int = ~(sum & 0xFF)+1 object ChecksumAccumulator { def calculate(s: String): Int = val acc = new ChecksumAccumulator for (c <- s) acc.add(c.tobyte) acc.checksum()
Rational Numbers Example class Rational(n: Int, d: Int) { require(d!= 0) def * (that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom) private val g = gcd(n.abs, d.abs) val numer = n / g val denom = d / g def this(n: Int) = this(n, 1) def + (that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) def + (i: Int): Rational = new Rational(numer + i * denom, denom) def * (i: Int): Rational = new Rational(numer * i, denom) override def tostring = numer +"/"+ denom private def gcd(a: Int, b: Int):Int = if (b == 0) a else gcd(b, a % b) constructors if-else yields value infix operators
Implicit Conversions implicit def inttorational(x: Int) = new Rational(x) scala> val r = new Rational(2,3) r: Rational = 2/3 scala> 2 * r res16: Rational = 4/3
For-loops val fileshere = (new java.io.file(".")).listfiles for ( file <- fileshere if file.isfile; if file.getname.endswith(".scala") ) println(file) for-loops mapped to calling methods map, flatmap, foreach, and filter typically implemented by Iterable trait
For-loops val fileshere = (new java.io.file(".")).listfiles def filelines(file: java.io.file) = scala.io.source.fromfile(file).getlines.tolist val forlinelengths = for { file <- fileshere if file.getname.endswith(".scala") line <- filelines(file) trimmed = line.trim if trimmed.matches(".*for.*") yield trimmed.length nested iterations for may yields a value
No break and continue int i = 0; // This is Java boolean foundit = false; while (i < args.length) { if (args[i].startswith("-")) { i = i + 1; continue; if (args[i].endswith(".scala")) { foundit = true; break; i = i + 1; def searchfrom(i: Int): Int = if (i >= args.length) -1 else if (args(i).startswith("-")) searchfrom(i + 1) else if (args(i).endswith(".scala")) i else searchfrom(i + 1) val i = searchfrom(0) there is no break and continue a way around is to use if-else tail recursion
First class functions scala> increase = (x: Int) => x + 9999 increase: (Int) => Int = <function> scala> increase(10) res2: Int = 10009 functions are first-class objects (...) on an object leads to calling method apply(...) scala> somenumbers.filter(x => x > 0) res8: List[Int] = List(5, 10) types inferred from context scala> somenumbers.filter(_ > 0) res9: List[Int] = List(5, 10) placeholder syntax
First class functions def sort(xs: Array[Int]): Array[Int] = { if (xs.length <= 1) xs else { val pivot = xs(xs.length / 2) Array.concat( sort(xs filter (pivot >)), xs filter (pivot ==), sort(xs filter (pivot <))) partially applied functions >, ==, < are methods of class Int
New control structures def withprintwriter(file: File)(op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) finally { writer.close() currying curly braces may be used instead of parentheses when just one parameter is supplied val file = new File("date.txt") withprintwriter(file) { writer => writer.println(new java.util.date)
By-name parameters def bynameassert(predicate: => Boolean) = if (assertionsenabled &&!predicate) throw new AssertionError bynameassert(5 > 3) syntactic sugar for writing () => 5>3
Traits trait Ordered[T] { def compare(that: T): Int def <(that: T): Boolean = (this compare that) < 0 def >(that: T): Boolean = (this compare that) > 0 def <=(that: T): Boolean = (this compare that) <= 0 def >=(that: T): Boolean = (this compare that) >= 0 class Rational(n: Int, d: Int) extends Ordered[Rational] { //... def compare(that: Rational) = (this.numer * that.denom) - (that.numer * this.denom) trait is something as Java interface but method definitions and attributes are allowed trait is basically a mixin abstract classes and single inheritance rule are still present
Trait as mixins class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal with Furry with FourLegged which method to call? linearization diamond inheritance all ancestors are in fact virtual
Matching def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" case _ => "something else" def generalsize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size case _ => -1 matching against a value matching against a type it binds parameters of the case // match only positive integers case n: Int if 0 < n =>... matching guards // match only strings starting with the letter `a' case s: String if s(0) == 'a' =>...
Matching constructors sealed abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr def simplifytop(expr: Expr): Expr = expr match { case UnOp("-", UnOp("-", e)) => e case BinOp("+", e, Number(0)) => e case BinOp("*", e, Number(1)) => e case _ => expr // Double negation // Adding zero // Multiplying by one case classes sealed modifier to denote complete enumeration of options expr match { case List(0, _*) => println("found it") case _ => possible with any class using extractors
Patterns everywhere scala> val mytuple = (123, "abc") mytuple: (Int, java.lang.string) = (123,abc) scala> val (number, string) = mytuple number: Int = 123 patterns in assignment string: java.lang.string = abc scala> val exp = new BinOp("*", Number(5), Number(1)) exp: BinOp = BinOp(*,Number(5.0),Number(1.0)) scala> val BinOp(op, left, right) = exp op: String = * left: Expr = Number(5.0) right: Expr = Number(1.0) val second: List[Int] => Int = { case x :: y :: _ => y cases in fact define a function with multiple entry points
Exceptions import java.io.filereader import java.io.filenotfoundexception import java.io.ioexception try { val f = new FileReader("input.txt") // Use and close file catch { case ex: FileNotFoundException => // Handle missing file case ex: IOException => // Handle other I/O error catch-block uses the match expressions syntax
Variance annotations class Queue[+T] private (val list: List[T]) { def head: T = list.head def tail: Queue[T] = { new Queue(list.tail) def append[u >: T](x: U) = new Queue[U]((x :: list.reverse).reverse) override def tostring = list.tostring object Queue { def empty[t] = new Queue[T](Nil) class Queue is covariant in its type parameter scala> val q = Queue.empty[String].append("Test") q: Queue[String] = List(Test) scala> val r : Queue[Any] = q r: Queue[Any] = List(Test)
Abstract types class Food abstract class Animal { type SuitableFood <: Food def eat(food: SuitableFood) class Grass extends Food class Cow extends Animal { type SuitableFood = Grass override def eat(food: Grass) { class Fish extends Food scala> val bessy: Animal = new Cow bessy: Animal = Cow@674bf6 method eat in class Cow accepts only instances of Grass or of its subclasses scala> bessy eat (new Fish) <console>:10: error: type mismatch; found : Fish required: bessy.suitablefood bessy eat (new Fish)
Support for XML scala> val joe = <employee name="joe" rank="code monkey" serial="123"/> joe: scala.xml.elem = <employee rank="code monkey" name="joe" serial="123"></employee> scala> joe \ "@name" res15: scala.xml.nodeseq = Joe parser can recognize XML and create instances of scala.xml.node scala> joe \ "@serial" res16: scala.xml.nodeseq = 123 def proc(node: scala.xml.node): String = node match { case <a>{contents</a> => "It's an a: "+ contents case <b>{contents</b> => "It's a b: "+ contents case _ => "It's something else." XML in matches braces used for escaping
Actors val echoactor = actor { while (true) { receive { case msg => println("received message: "+ msg) scala> echoactor! "hi there" received message: hi there actor is a thread actors may exchange messages synchronized keyword and data sharing is possible but discouraged
Reusing threads object NameResolver extends Actor { import java.net.{inetaddress, UnknownHostException def act() { react { case (name: String, actor: Actor) => actor! getip(name); act() case "EXIT" => println("name resolver exiting.") // quit case msg => println("unhandled message: "+ msg); act() def getip(name: String): Option[InetAddress] = { try { Some(InetAddress.getByName(name)) catch { case _:UnknownHostException => None react method never returns thread is reused for other actors possible to have a large number of actors
Futures val ft = a!! Msg // send message, ft is a future... val res = ft() // await future ft val ft1 = a!! Msg val ft2 = b!! Msg val ft3 = c!! Msg... val results = awaitall(500, ft1, ft2, ft3) // returns a List[Option[Any]] holding the results val res = awaiteither(ft1, ft2)
Other not covered nifty features imports access modifiers tuples, lists and many others...
Conclusion Quite a good language (better than Java) Actors are a powerful concept for dealing with concurrency and asynchrony The IDE support is falling behind But the best of all... it's Java bytecode!!! So integration with Java and it's libraries is straightforward