Jan 15, 2015
Where objects and functions meet
by Mario [email protected]: @mariofusco
day 1: hour 1: Object Orientation Classes, Objects and Traits Generic Types Case Classes, Patter Matching and Tuples hour 2: Functional Programming First-class and Anonymous Functions Higer-Order Functions and Curry Implicit Parameters and Conversions Using Scala features to create a simple DSLday 2: hour 1: Using OO and FP together Structural Typing Scala Collections For-Comprehensions Options and Monads hour 2: Concurrency Abstractions for Concurrency Actors and Remote Actors
Do we need a new language?
Keep It SimpleVs.
Do More With Less
Why Scala?
object-oriented
functional
Java compatible
concise
extensible
statically typed
concurrent
scriptable
The first Scala classclass Rational(n: Int, d: Int) {
val num = n val den = d
def this(n: Int) = this(n, 1)
def + (that: Rational): Rational = new Rational(num * that.den + that.num * den, den * that.den)
def + (i: Int): Rational = new Rational(num + i * den, den)
override def toString = "" + num + "/" + den}
class Rational(n: Int = 1, d: Int = 1) extends AnyRef { ....}
Rational(n = 2, d = 3)
Rational(d = 3, n = 2)
Rational(d = 3)Rational()
Named and default parameters
Scala’s type hierarchy
Object
object RationalOne extends Rational(1)
object Rational { def apply(n: Int) = new Rational(n) def apply(n: Int, d: Int) = new Rational(n, d)}
val one = RationalOneval two = Rational(1) + one
(Companion)
val two = Rational(1).+(one)
Traits
class Animal { def eat(): Unit }trait Mammal extends Animal { def giveBirth(): Mammal }trait HasWings extends Animal { def fly(): Unit }trait HasLegs extends Animal { def walk(): Unit }
class Snake extends Animalclass Frog extends Animal with HasLegsclass Cat extends Animal with Mammal with HasLegsclass Bat extends Animal with Mammal with HasWingsclass Chimera extends Animal with Mammal with HasWings with HasLegs
Let’s put all togethertrait IntSet { def contains(x: Int): Boolean def notContains(x: A) = !contains(x) def add(x: Int): IntSet}
object EmptySet extends IntSet { def contains(x: Int): Boolean = false def add(x: Int): IntSet = new NonEmptySet(x, EmptySet, EmptySet)}
class NonEmptySet(elem: Int, left: IntSet, right: IntSet) extends IntSet { def contains(x: Int): Boolean = if (x < elem) left contains x else if (x > elem) right contains x else true def add(x: Int): IntSet = if (x < elem) new NonEmptySet(elem, left add x, right) else if (x > elem) new NonEmptySet(elem, left, right add x) else this}
Generic Typestrait Set[A <: Ordered[A]] { def contains(x: A): Boolean def add(x: A): Set[A]}
class EmptySet[A <: Ordered[A]] extends Set[A] { def contains(x: A): Boolean = false def add(x: A): Set[A] = new NonEmptySet(x, new EmptySet[A], new EmptySet[A])}
class NonEmptySet[A <: Ordered[A]] (elem: A, left: Set[A], right: Set[A]) extends Set[A] { def contains(x: A): Boolean = if (x < elem) left contains x else if (x > elem) right contains x else true def add(x: A): Set[A] = if (x < elem) new NonEmptySet(elem, left add x, right) else if (x > elem) new NonEmptySet(elem, left, right add x) else this}
Case classes
sealed trait Exprcase class Var(name: String) extends Exprcase class Number(num: Double) extends Exprcase class Unop(op: String, arg: Expr) extends Exprcase class Binop(op: String, l: Expr, r: Expr) extends Expr
Pattern matching
def simplify(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}
// Simplify double negation: simplified = Var("x")val simplified = simplify(Unop("-", Unop("-", Var("x"))))
Tuples
val pair = (2, "items")println(pair._1) // prints 2println(pair._2) // prints items
def divmod(x: Int, y: Int): (Int, Int) = (x / y, x % y)
divmod(x, y) match { case (n, d) => println("quotient: " + n + ", rest: " + d)}
First-Class Functiondef sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b)
def id(x: Int): Int = xdef sumInts(a: Int, b: Int): Int = sum(id, a, b)
def square(x: Int): Int = x * xdef sumSquares(a: Int, b: Int): Int = sum(square, a, b)
def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a + 1, b)
def sumSquares(a: Int, b: Int): Int = if (a > b) 0 else a * a + sumSquares(a + 1, b)
def sumInts(a: Int, b: Int): Int = sum((x: Int) => x, a, b)def sumSquares(a: Int, b: Int): Int = sum((x: Int) => x * x, a, b)
Anonymous Function
Higher-Order Functions and Currydef sum(f: Int => Int): (Int, Int) => Int = { def sumF(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sumF(a + 1, b) sumF}
def sumInts = sum(x => x)def sumSquares = sum(x => x * x)
val sum1To10 = sumInts(1, 10)
val sum1To10 = sum(x => x)(1, 10)
def sum(f: Int => Int)(a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f)(a + 1, b)
def sum(a: Int, b: Int)(f: Int => Int): Int = if (a > b) 0 else f(a) + sum(a + 1, b)(f)
val sum1To10 = sum(1, 10) { x => x }
Implicit parametersabstract class Aggregator[A] { def unit: A def add(x: A, y: A): A}object stringAggregator extends Aggregator[String] { def unit = "" def add(x: String, y: String): String = x concat y}object intAggregator extends Aggregator[Int] { def unit = 0 def add(x: Int, y: Int): Int = x + y}
def sum[A](l: List[A]) (a: Aggregator[A]): A = if (l.isEmpty) a.unit else a.add(l.head, sum(l.tail)(a))
sum(List("a", "b", "c"))(stringAggregator)sum(List(1, 2, 3))(intAggregator)
(implicit a: Aggregator[A]): A =
Implicit conversion
val a = new Rational(2, 3)val b = a + 2 // = 8/3val c = 2 + a // Compilation Error
implicit def intToRational(x: Int) = new Rational(x)val c = 2 + a // = 8/3
View bound
trait Set[A <% Rational]
Structural Typing(duck typing done right)
class Duck { quack() { println "quack" } }doQuack(new Duck)
doQuack(d) { d.quack() }
class Dog { barf() { println "barf" } }doQuack(new Dog)
def doQuack(d:{ def quack():Unit }) = d.quack()
class Duck { def quack() = println "quack" }doQuack(new Duck)
class Dog { def barf() = println "barf" }doQuack(new Dog)runtime error
compilationerror
Duck typing is the dynamic mechanism that allows to discover a dog cannot say quack only at runtime ... in production ... on friday evening
Listsval letters: List[String] = List("a", "b", "c", "d")
val emptyList = Nil
val letters = "a" :: "b" :: "c" :: "d" :: Nil
x :: ys is equivalent to ys.::(x) // infix operator == right associative
xs ::: ys is equivalent to ys.:::(xs)
letters.head = "a"letters.tail = List("b", "c", "d")
def sortedInsert(x: Int, xs: List[Int]): List[Int] = xs match { case List() => List(x) case y :: ys => if (x <= y) x :: xs else y :: sortedInsert(x, ys)}
Higher-Order Functions on Lists
val animals = List("dog", "cat", "horse", "rabbit")animals.foreach(s => println(s))
def foreach(f: A => Unit) { this match { case Nil => () case x :: xs => f(x); xs.foreach(f) }}
animals.foreach(println _)animals.foreach(println)
animals.map(s => s + "s")animals.mkString(", ")animals.count(s => s.length > 3)animals.remove(s => s.length > 3)animals.sort((s,t) => s.charAt(1) < t.charAt(1))
animals.foldLeft(0)((s,t) => s + t.length)(0 /: animals)(_ + _.length)
For-Comprehensions
for (p <- persons if p.age > 20) yield p.name
persons filter (p => p.age > 20) map (p => p.name)
for { p <- persons // Generators c <- p.children if c.name startsWith "A" // Filter} yield p.name // Map
Given n>0 find all pairs i and j where 1 ≤ j ≤ i ≤ n and i+j is prime
List.range(1, n) .map(i => List.range(1, i).map(x => (i, x))) .foldRight(List[(Int, Int)]()) {(xs, ys) => xs ::: ys} .filter(pair => isPrime(pair._1 + pair._2))
List.range(1, n) .flatMap(i => List.range(1, i).map(x => (i, x))) .filter(pair => isPrime(pair._1 + pair._2))
Where: class List[A] { def flatMap[B](f: A => List[B]): List[B] }
for { i <- List.range(1, n) j <- List.range(1, i) if isPrime(i + j) } yield {i, j}
List.range(1, n) .flatMap(i => List.range(1, i) .filter(j => isPrime(i+j)) .map(j => (i, j)))
Options
Tony Hoare, who invented the null reference in 1965 while working on an object oriented language called ALGOL W, called
its invention his “billion dollar mistake”
val capitals = Map("Italy" -> "Rome", "Switzerland" -> "Bern", "Germany" -> "Berlin" , "France" -> "Paris")
println(capitals.get("Italy")) // Some(Rome)println(capitals.get("Spain")) // None
println(capitals.get("Italy").get) // Romeprintln(capitals.get("Spain").get) // thorws Exceptionprintln(capitals.get("Spain").getOrElse("Unknown")) // Unknown
Options as Monadsdef map[B](f: A => B): M[B]def flatMap[B](f: A => M[B]): M[B]def filter(p: A => Boolean): M[A]
def readPositiveIntParam(params: Map[String, String], name: String): Int = params get name flatMap stringToInt filter (_ > 0) getOrElse 0
def stringToInt(string: String) : Option[Int] = try { Some(string.toInt)} catch { case _ : java.lang.NumberFormatException => None}
def readPositiveIntParam(params: Map[String, String], name: String): Int = (for { param <- params get name; value <- stringToInt(param) if (value > 0) } yield value) getOrElse 0
val params = Map("a" -> "5", "b" -> "false", "c" -> "-3")val readPositiveIntParam(params, "a") // == 5val readPositiveIntParam(params, "b") // == 0 – Same for "c" and "d"
Signals and Monitors
def synchronized[A] (e: => A): Adef wait()def wait(msec: Long)def notify()def notifyAll()
class SyncVar[A] { private var isDefined: Boolean = false private var value: A = _ def get = synchronized { while (!isDefined) wait() value } def set(x: A) = synchronized { value = x; isDefined = true; notifyAll() } def isSet: Boolean = synchronized { isDefined } def unset = synchronized { isDefined = false }}
SyncVar
Futuresdef future[A](p: => A): Unit => A = { val result = new SyncVar[A] fork { result.set(p) } (() => result.get)}
val x = future(someLengthyComputation)anotherLengthyComputationval y = f(x()) + g(x())
class Lock { var available = true def acquire = synchronized { while (!available) wait() available = false } def release = synchronized { available = true notify() }}
Semaphores
class MailBox { def send(msg: Any) def receive[A](f: PartialFunction[Any, A]): A def receiveWithin[A](msec: Long)(f: PartialFunction[Any, A]): A}
Mailboxes
Actors
class PrinterActor extends Actor { def act() { while(true) { receive { case msg => println("Received message: " + msg) } } }}
val printerActor = new PrinterActorprinterActor.start
printerActor ! "hi there“ // prints "Received message: hi there"printerActor ! 23 // prints "Received message: 23"
import scala.actors.Actor._
val printerActor = actor { while(true) { receive { case s: String => println("I got a String: " + s) case i: Int => println("I got an Int: " + i.toString) case _ => println(" I don’t know what I got ") } }}
printerActor ! "hi there" // prints “I got a String: hi there”printerActor ! 23 // prints “I got an Int: 23”printerActor ! 3.33 // prints “I don’t know what I got”
Creating Actors with the actor method
react instead of receive (when possible)
import scala.actors.Actor._
val printerActor = actor { loop { react { case s: String => println("I got a String: " + s) case i: Int => { println("I got an Int: " + i.toString) println(“Waiting for another Int") react { case j: Int => println(“Another Int: " + j.toString) } } case _ => exit } }}
Message types
! send an asynchronous message which means that the sending actor does not wait until the message is received; its execution continues immediately. All actors have a mailbox which buffers incoming messages until they are processed
!? send a synchronous message: causes the sending actor to wait until a response is received which is then returned. There is an overloaded variant taking a timeout and returning an Option[Any] instead of Any
!! similar to !? In the sense that it allows to get an answer from the receiver. However, instead of blocking the sending actor until a response is received, it returns Future instance that can be used to retrieve the receiver’s response once it is available
Remote Actorsactor { // SERVER ACTOR RemoteActor.classLoader = getClass().getClassLoader() alive(9000) // starts remote actor listening on the given port register('Server, self) // registers the actor using the symbol loop { receive { case Message => sender ! ... } }}
actor { // CLIENT ACTOR trapExit = true // listens exit of linked actors RemoteActor.classLoader = getClass().getClassLoader() alive(9001) val server = select(Node("127.0.0.1", 9000), 'Server) link(server) // links this actor to the server one server ! Message // sends a Message to the server}