27
DISTRIBUTED SYSTEMS RESEARCH GROUP http://nenya.ms.mff.cuni.cz CHARLES UNIVERSITY PRAGUE Faculty of Mathematics and Physics Scala Programming Language Tomáš Bureš

Scala Programming Language - Univerzita Karlovad3s.mff.cuni.cz/teaching/seminars/2009-10-14-Bures-ScalaLang.pdf · What is Scala • Programming language class-based imperative with

Embed Size (px)

Citation preview

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

Some basic notes

• Primitive types same as in Java

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)

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