Scala Programming Language - Univerzita...
Transcript of Scala Programming Language - Univerzita...
DISTRIBUTED SYSTEMS RESEARCH GROUPhttp://nenya.ms.mff.cuni.cz
CHARLES UNIVERSITY PRAGUEFaculty of Mathematics and Physics
Scala Programming Language
Tomáš Bureš
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
Objects and classes
// In file Summer.scala
import ChecksumAccumulator.calculate
object Summer {
def main(args: Array[String]) {
for (arg <- args)
println(arg +": "
+ calculate(arg))
}
}
// In file ChecksumAccumulator.scala
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()
}
}
• main method• singleton objects• classes• vals & vars• type parameterization• type inference• semicolon inference
Rational Numbers Example
class Rational(n: Int, d: Int) {
require(d != 0)
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 * (that: Rational): Rational =
new Rational(numer * that.numer,
denom * that.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
scala> someNumbers.filter(x => x > 0)
res8: List[Int] = List(5, 10)
scala> someNumbers.filter(_ > 0)
res9: List[Int] = List(5, 10)
• functions are first-class objects• (...) on an object leads to calling
method apply(...)
• types inferred from context
• 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()
}
}
val file = new File("date.txt")
withPrintWriter(file) {
writer => writer.println(new java.util.Date)
}
• currying• curly braces may be used instead of parentheses
when just one parameter is supplied
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
}
// match only positive integers
case n: Int if 0 < n => ...
// match only strings starting with the letter `a'
case s: String if s(0) == 'a' => ...
• matching against a value• matching against a type• it binds parameters of the case
• matching guards
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 // Double negation
case BinOp("+", e, Number(0)) => e // Adding zero
case BinOp("*", e, Number(1)) => e // Multiplying by one
case _ => expr
}
expr match {
case List(0, _*) => println("found it")
case _ =>
}
• case classes• sealed modifier to denote complete enumeration of options
• 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
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
}
• patterns in assignment
• 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)
}
scala> val q = Queue.empty[String].append("Test")
q: Queue[String] = List(Test)
scala> val r : Queue[Any] = q
r: Queue[Any] = List(Test)
• class Queue is covariant in its type parameter
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
scala> bessy eat (new Fish)
<console>:10: error: type mismatch;
found : Fish
required: bessy.SuitableFood
bessy eat (new Fish)
• method eat in class Cow accepts only instances of Grass or of its subclasses
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
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."
}
• parser can recognize XML and create instances of scala.xml.Node
• 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)