From Java to Scala - advantages and possible risks
Oleksii PetinovScala engineer,
Newground
Scala = “scalable”
● Scalable = extensiblesmall core – everything else are libraries. Efficient for both small utilities or large-scale systems. Very good for building libraries (Play, Akka etc)
var capital = Map("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
● Multiparadigm = FP + OOP
Who is using Scala
● Foursquare
● UK Guardian
● Sony Pictures Entertainment
● SAP
● Xerox
● Novell
● Netflix
● Tumblr (partially)
Compatibility and learning
● Very good Java interop● Easy to get into – allows (almost) Java style
programming
Concise
Java:
class MyClass {
private int index;
private String name;
public MyClass(int index, String name) {
this.index = index;
this.name = name;
}
}
Scala:
case class MyClass(index: Int, name: String)
High-level
Java:
boolean nameHasUpperCase = false;
for (int i = 0; i < name.length(); ++i) {
if (Character.isUpperCase(name.charAt(i))) {
nameHasUpperCase = true;
break;
}
}
Scala:
val nameHasUpperCase = name.exists(_.isUpper)
Staticly-typed
● Advanced static type system
● Verifiable properties
● Sophisticated type inference system
val x = new HashMap[Int, String]()
val x: Map[Int, String] = new HashMap()
● Duck typing
def callSpeak[A <: { def speak(): Unit }](obj: A) {
// code here ...
obj.speak()
}
FunctionalWith side-effects
class Cafe {
def buyCoffee(cc: CreditCard): Coffee = {
val cup = new Coffee()
cc.charge(cup.price)
cup
}
}
With side-effects and payments object:
class Cafe {
def buyCoffee(cc: CreditCard, p: Payments): Coffee = {
val cup = new Coffee()
p.charge(cc, cup.price)
cup
}
}
Side-effects free – returning Charge object:
class Cafe {
def buyCoffee(cc: CreditCard): (Coffee, Charge) = {
val cup = new Coffee()
(cup, Charge(cc, cup.price))
}
}
Small core, extensibility
Library class `scala.BigInt` feels natural – like part of the language:
def factorial(x: BigInt): BigInt = if (x == 0) 1 else x * factorial(x - 1)
In Java it feels like more like library type:
import java.math.BigInteger
def factorial(x: BigInteger): BigInteger =
if (x == BigInteger.ZERO)
BigInteger.ONE
else
x.multiply(factorial(x.subtract(BigInteger.ONE)))
High level - predicates
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 <)))
}
}
High level – control structuresDefinition (open a resource, operate on it, and then close the resource):
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
Usage:
val file = new File("date.txt")
withPrintWriter(file) {
writer => writer.println(new java.util.Date)
}
DSLs (internal)Scalatest:
test("pop is invoked on an empty stack") { val emptyStack = new Stack[String]
evaluating { emptyStack.pop() } should produce [NoSuchElementException]
emptyStack should be ('empty)
}
Squeryl:
def songCountByArtistId: Query[GroupWithMeasures[Long,Long]] =
from(artists, songs)((a,s) =>
where(a.id === s.artistId)
groupBy(a.id)
compute(count)
)
Traits trait Philosophical {
def philosophize() {
println("I consume memory, therefore I am!")
}
}
class Animal
trait HasLegs
class Frog extends Animal with Philosophical with HasLegs {
override def toString = "green"
}
Inheritance “diamond” problem
Operator notation for methods
scala> val sum = 1 + 2 // Scala invokes (1).+(2)
sum: Int = 3
scala> val a = List(1,2,3)
a: List[Int] = List(1, 2, 3)
scala> val b = List(4,5,6)
b: List[Int] = List(4, 5, 6)
scala> a ::: b // Scala invokes b.:::(a)
res0: List[Int] = List(1, 2, 3, 4, 5, 6)
Local functions
def processFile(filename: String, width: Int) { def processLine(line: String) {
if (line.length > width)
println(filename +": "+ line)
}
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(line)
}
Function literals and values
● Function literals and valuesscala> val increase = (x: Int) => x + 1
increase: (Int) => Int = <function1>
scala> increase(10)
res0: Int = 11
● Function literals as predicatessomeNumbers.filter(x => x > 0)
Named and default arguments● Named
scala> def speed(distance: Float, time: Float): Float = distance / time
speed: (distance: Float,time: Float)Float
scala> speed(100, 10)
res28: Float = 10.0
scala> speed(time = 10, distance = 100)
res30: Float = 10.0
● Default
def printTime(out: java.io.PrintStream = Console.out) =
out.println("time = "+ System.currentTimeMillis())
Handling errors: exceptions def purchaseCoffee(money: Int): Coffee =
brewCoffee(buyBeans(money))
def buyBeans(money: Int): Beans =
if (money < price)
throw new Exception(s"Not enough money to buy beans for a coffee, need $price")
else
new Beans
def brewCoffee(beans: Beans): Coffee = {
// simulate a faulty grinder that fails 25% of the time
if (Math.random < 0.25)
throw new Exception("Faulty grinder failed to grind beans!")
else
new Coffee
}
Handling exceptions functionally case class FailureReason(reason: String)
def purchaseCoffee(money: Int): Either[FailureReason, Coffee] =
for {
beans <- buyBeans(money).right
coffee <- brewCoffee(beans).right
} yield coffee
def buyBeans(money: Int): Either[FailureReason, Beans] =
if (money < price)
Left(FailureReason(s"Not enough money to buy beans for a coffee, need $price"))
else
Right(new Beans)
def brewCoffee(beans: Beans): Either[FailureReason, Coffee] = {
if (Math.random < 0.25)
Left(FailureReason("Faulty grinder failed to grind beans!"))
else
Right(new Coffee)
}
Partially applied functionsOriginal function:
def withTax(cost: Float, state: String) = { /* Some complicated lookup table */ }
Partially applied function:
val locallyTaxed = withTax(_: Float, "NY")
val costOfApples = locallyTaxed(price("apples"))
Converting method to function value:
val func = method //Wrong
val func :Int => Int = method //This works
val func = method _ //Or like this
Scala complexity● Scala is not complex, but it allows you to compactly
express complex ideas. ● Understanding compact expression of complex
ideas can be hard.val x:Option[Int] = 2.some // scalaz enrichment for options
val y:Option[Int] = 3.some
val z:Option[Int] = 5.some
// With scalaz we can do the following instead of for or maps
// First we need to put the function in the right form, curried.
// To understand why please read the references I've given below.
val addInts = ( (a:Int, b:Int, c:Int) => a + b + c ).curried
val sum = x <*> (y <*> (z map addInts)) // Some(10)
Tail recursion
Tail-recursive function:
@tailrec
def approximate(guess: Double): Double =
if (isGoodEnough(guess)) guess
else approximate(improve(guess))
}
Compiles to loop and performance-wise is the same as:
def approximateLoop(initialGuess: Double): Double = {
var guess = initialGuess
while (!isGoodEnough(guess))
guess = improve(guess)
guess
}
Control absatractions private def filesHere = new java.io.File(".").listFiles
def filesMatching(query: String,
matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}
def filesEnding(query: String) =
filesMatching(query, _.endsWith(_))
def filesContaining(query: String) =
filesMatching(query, _.contains(_))
def filesRegex(query: String) =
filesMatching(query, _.matches(_))
Currying (see slide 11)
scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Int
scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int
scala> val second = first(1)
second: (Int) => Int = <function1>
scala> second(2)
res6: Int = 3
Scala types (everything is object)
Objects – natural singletones abstract class Browser {
val database: Database
def recipesUsing(food: Food) =
database.allRecipes.filter(recipe =>
recipe.ingredients.contains(food))
}
abstract class Database {
def allFoods: List[Food]
}
object SimpleDatabase extends Database {
def allFoods = List(Apple, Orange, Cream, Sugar)
}
object SimpleBrowser extends Browser {
val database = SimpleDatabase
}
Enumerations and case objects
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
currency match {
case EUR =>
case UnknownCurrency(code) =>
}
Dependency Injection
● Non-framework DI Patterns (constructor, setter, cake, thin-cake, implicits, scalaz.Reader monad)
● DI frameworks (spring, guice, Subcut, MacWire, Scaldi)
Package objects // In file bobsdelights/package.scala
package object bobsdelights {
def showFruit(fruit: Fruit) {
import fruit._
println(name +"s are "+ color)
}
}
// In file PrintMenu.scala
package printmenu
import bobsdelights.showFruit
object PrintMenu {
def main(args: Array[String]) {
for (fruit <- Fruits.menu) {
showFruit(fruit)
}
}
}
Case classes 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
scala> val v = Var("x")
v: Var = Var(x)
scala> val op = BinOp("+", Number(1), v)
op: BinOp = BinOp(+,Number(1.0),Var(x))
scala> v.name
res0: String = x
scala> println(op)
BinOp(+,Number(1.0),Var(x))
scala> op.right == Var("x")
res3: Boolean = true
scala> op.copy(operator = "-")
res4: BinOp = BinOp(-, Number(1.0),Var(x))
Pattern matching expr match {
case BinOp(op, left, right) =>
println(expr + " is a binary operation")
case somethingElse => "not expression: "+ somethingElse
case _ =>
}
case 0 => "zero"
case BinOp("+", e, Number(0)) =>
println("a deep match")
case List(0, _, _) => println("found it")
case s: String => s.length
case BinOp("+", x, y) if x == y =>
BinOp("*", x, Number(2))
Covering all cases
Sealed classes 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
Option instead of null scala> var x : Option[String] = None
x: Option[String] = None
scala> x.get
java.util.NoSuchElementException: None.get in
scala> x.getOrElse("default")
res0: String = default
scala> x = Some("Now Initialized")
x: Option[String] = Some(Now Initialized)
scala> x.get
res0: java.lang.String = Now Initialized
scala> x.getOrElse("default")
res1: java.lang.String = Now Initialized
val myData = map.get(userId).map(doFunction).map(toHtml)
println(myData.getOrElse(noDataHtml))
Lists
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
val oneTwoThree = 1 :: 2 :: 3 :: Nil
list.head // Returns the first element in the thrill list
list.init // Returns a list of all but the last element in the thrill list
list.tail // Returns the thrill list minus its first element
Scala collections: uniformity
HOF and collections - 1 {
val names = Array("Sam", "Pamela", "Dave", "Pascal", "Erik")
val filteredNames = names filter(_.contains(”am”)) toList
}
{
val names = Array("Sam", "Pamela", "Dave", "Pascal", "Erik")
val nameList = names.zipWithIndex collect {
case (c, index) if (c.length <= index+1) => c
} toList
}
{
val nameList1 = List("Anders", "David", "James", "Jeff", "Joe", "Erik")
nameList1 foreach { n => println(s"Hello! $n") }
}
{
val map = Map("UK" -> List("Bermingham", "Bradford", "Liverpool"),
"USA" -> List("NYC", "New Jersey", "Boston", "Buffalo"))
val cities = map.values.flatten
}
{
val numbers = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
val first4 = numbers take(4) toList
}
HOF on collections - 2 {
val moreNames = Array("Sam", "Samuel", "Dave", "Pascal", "Erik", "Sid")
val sNames = moreNames takeWhile(_ startsWith "S") toList
}
{
val vipNames = Array("Sam", "Samuel", "Samu", "Remo", "Arnold", "Terry")
val skippedList = vipNames drop(3) toList
}
{
val numbers = Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20)
val skippedList = numbers dropWhile(_ < 10)
}
{
val friends = Array("Sam", "Pamela", "Dave", "Anders", "Erik")
val sortedFriends = friends.sorted
}
{
val friends = Array("Sam", "Pamela", "Dave", "Anders", "Erik")
friends = friends.sortBy(_.length)
}
HOF and collections - 3
val myMap = Map("Brown Bear" -> 635, "Grizzly Bear" -> 360, "American Black Bear" -> 270, "Polar Bear" -> 680)
myMap.filter((x) => x._2 > 300)
myMap.filter(_._2 > 300)
myMap.filter { case (x, y) => y > 300 }
myMap.foldLeft(0)((sum, v) => sum + v._2) / myMap.size
val animals = Set("newt", "armadillo", "cat", "guppy")
animals.foreach(println)
val lengthsMapped = animals.map(animal => animal.length)
val nums = Set(1,2,3,4)
nums.map(x=>x+1).map(x=>x*x)
val nums2 = nums.map(x=>x+1)
nums2.map(x=>x*x)
nums.map(x=>x+1).map(x=>x*x).map(x=>x-1).map(x=>x*(-1)).map(x=>"The answer is: " + x)
Mutable collections
● Array Buffers● List Buffers● StringBuilders● Linked Lists● Double Linked Lists● Mutable Lists● Queues● Array Sequences● Stacks● Array Stacks● Hash Tables● Weak Hash Maps● Concurrent Maps● Mutable Bitsets
Type parametrization - 1 class Queue[T](
private val leading: List[T],
private val trailing: List[T]
) {
private def mirror =
if (leading.isEmpty)
new Queue(trailing.reverse, Nil)
else
this
def head = mirror.leading.head
def tail = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def enqueue(x: T) =
new Queue(leading, x :: trailing)
}
Type parametrization - covariance scala> def doesNotCompile(q: Queue) {}
<console>:5: error: trait Queue takes type parameters
def doesNotCompile(q: Queue) {}
ˆ
scala> def doesCompile(q: Queue[AnyRef]) {}
doesCompile: (Queue[AnyRef])Unit
trait Queue[+T] { ... }
trait Queue[-T] { ... }
// if a generic parameter type appears as the type of a method parameter,
// the containing class or trait may not be covariant in that type parameter
class Queue[+T] {
def enqueue(x: T) = ???
}
Contravariance and lower bounds
trait OutputChannel[-T] {
def write(x: T)
}
trait Function1[-S,+T] {
def apply(x: S): T
}
Upper bounds class Animal
class Dog extends Animal
class Puppy extends Dog
object ScalaUpperBoundsTest {
def main(args: Array[String]) {
val animal = new Animal
val dog = new Dog
val puppy = new Puppy
val animalCarer = new AnimalCarer
//animalCarer.display(animal)
animalCarer.display(dog)
animalCarer.display(puppy)
}
}
class AnimalCarer{
def display [T <: Dog](t: T){
println(t)
}
}
Implicit conversions
val letters = "ABCEDEFG".foldLeft("")((acc, c) => acc + c + " ")
println(letters) // A B C E D E F G
object Predef extends LowPriorityImplicits with DeprecatedPredef {
...
@inline imlicit def augmentString(x: String): StringOps = new StringOps(x)
...
}
Implicit parameters
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PreferredPrompt,
drink: PreferredDrink) {
println("Welcome, "+ name +". The system is ready.")
print("But while you work, ")
println("why not enjoy a cup of "+ drink.preference +"?")
println(prompt.preference)
}
}
object JoesPrefs {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
}
Type classes - ordering
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord._
if (a < b) a else b
}
Type classes - JSONcase class FullName(firstName: String, lastName: String, middleName: Option[String] = None)
...
implicit object NameJsonWriter extends JsonFormat[FullName] {
override def write(obj: FullName): JsValue = JsObject(
”firstName” -> JsString(obj.firstName),
”lastName” -> JsString(obj.lastName),
”middleName” -> obj.middleName.map(JsString(_)).getOrElse(JsNull))
override def read(json: JsValue): FullName = {
json.asJsObject.getFields(”firstName”, ”lastName”, ”middleName”) match {
case Seq(JsString(firstName), JsString(lastName), JsString(middleName)) =>
FullName(firstName, lastName, Some(middleName))
case Seq(JsString(firstName), JsString(lastName)) =>
FullName(firstName, lastName)
case _ => throw new DeserializationException("FullName expected")
...
private def anyToAst[T:JsonWriter](any: T): JsValue = any.toJson
private def astToAny[T:JsonReader](ast: JsValue): T = ast.convertTo[T]
}
For comprehension case class Book(title: String, authors: String*)
val books: List[Book] = List(
Book("Structure and Interpretation of Computer Programs", "Abelson, Harold", "Sussman, Gerald J."),
Book("Principles of Compiler Design", "Aho, Alfred", "Ullman, Jeffrey" ),
Book("Programming in Modula-2", "Wirth, Niklaus"),
Book("Elements of ML Programming", "Ullman, Jeffrey"),
Book("The Java Language Specification", "Gosling, James", "Joy, Bill", "Steele, Guy", "Bracha, Gilad")
)
// Then, to find the titles of all books whose author's last name is "Gosling":
for (b <- books; a <- b.authors
if a startsWith "Gosling")
yield b.title
// Or, to find the titles of all books that have the string "Program" in their title:
for (b <- books if (b.title indexOf "Program") >= 0)
yield b.title
// Or, to find the names of all authors that have written at least two books in the database:
for (b1 <- books; b2 <- books if b1 != b2;
a1 <- b1.authors; a2 <- b2.authors if a1 == a2)
yield a1
Combinator parser (external DSL)trait ArithmExprParser extends JavaTokenParsers {
sealed abstract class Tree
case class Add(t1: Tree, t2: Tree) extends Tree
case class Sub(t1: Tree, t2: Tree) extends Tree
case class Mul(t1: Tree, t2: Tree) extends Tree
case class Div(t1: Tree, t2: Tree) extends Tree
case class Num(t: Double) extends Tree
def eval(t: Tree): Double = t match {
case Add(t1, t2) => eval(t1)+eval(t2)
case Sub(t1, t2) => eval(t1)-eval(t2)
case Mul(t1, t2) => eval(t1)*eval(t2)
case Div(t1, t2) => eval(t1)/eval(t2)
case Num(t) => t
}
lazy val expr: Parser[Tree] = term ~ rep("[+-]".r ~ term) ^^ {
case t ~ ts => ts.foldLeft(t) {
case (t1, "+" ~ t2) => Add(t1, t2)
case (t1, "-" ~ t2) => Sub(t1, t2)
}
}
lazy val term = factor ~ rep("[*/]".r ~ factor) ^^ {
case t ~ ts => ts.foldLeft(t) {
case (t1, "*" ~ t2) => Mul(t1, t2)
case (t1, "/" ~ t2) => Div(t1, t2)
}
}
lazy val factor = "(" ~> expr <~ ")" | num
lazy val num = floatingPointNumber ^^ { t => Num(t.toDouble) }
}
GET / controllers.Application.index
GET /ws controllers.Application.ws
Streamscala> List("a", ”b”, ”c”) zip (Stream from 1)
res5: List[(java.lang.String, Int)] = List((a,1), (b,2), (c,3))
scala> val s = 1 #:: {
| println(”HI”)
| 2
| } #:: {
| println("BAI”)
| 3
| } #:: Stream.empty
s: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> s(0)
res39: Int = 1
scala> s(1)
HI
res40: Int = 2
scala> s(2)
BAI
res41: Int = 3
scala> s
res43: scala.collection.immutable.Stream[Int] = Stream(1, 2, 3)
Concurrency – Futures - 1 import ExecutionContext.Implicits.global
val session = SocialNetwork.createSessionFor("user", SocialNetwork.credentials)
val f: Future[List[String]] = Future {
session.getRecentPosts()
}
f onComplete {
case Success(posts) => for (post <- posts) println(post)
case Failure(t) => println("An error has occured: " + t.getMessage)
}
f onSuccess { case posts => for (post <- posts) println(post) }
f onFailure { case t => println("An error has occured: " + t.getMessage) }
@volatile var totalA = 0
val text = Future { "na" * 16 + "BATMAN!!!" }
text onSuccess { case txt => totalA += txt.count(_ == 'a') }
text onSuccess { case txt => totalA += txt.count(_ == 'A') }
Concurrency – Futures - 2 val rateQuote = Future {
connection.getCurrentValue(USD)
}
{ // Version 1
rateQuote onSuccess { case quote =>
val purchase = Future {
if (isProfitable(quote)) connection.buy(amount, quote)
else throw new Exception("not profitable")
}
purchase onSuccess {
case _ => println("Purchased " + amount + " USD")
}
}
}
{ // Version 2
val purchase = rateQuote map { quote =>
If (isProfitable(quote)) connection.buy(amount, quote)
else throw new Exception("not profitable")
}
purchase onSuccess {
case _ => println("Purchased " + amount + " USD")
}
}
Concurrency – Futures - 3 {
val usdQuote = Future { connection.getCurrentValue(USD) }
val chfQuote = Future { connection.getCurrentValue(CHF) }
val purchase = for {
usd <- usdQuote
chf <- chfQuote if isProfitable(usd, chf)
} yield connection.buy(amount, chf)
purchase onSuccess { case _ => println("Purchased " + amount + " CHF") }
}
{
val purchase: Future[Int] = rateQuote map {
quote => connection.buy(amount, quote)
} recover {
case QuoteChangedException() => 0
}
}
Concurrency - Promise import scala.concurrent.ExecutionContext.Implicits.global
val p = Promise[T]()
val f = p.future
val producer = Future {
val r = produceSomething()
if (isInvalid(r))
p failure (new IllegalStateException)
else {
val q = doSomeMoreComputation(r)
p success r
}
continueDoingSomethingUnrelated()
}
val consumer = Future {
startDoingSomething()
f onSuccess { case r => doSomethingWithResult() }
}
Reactive - 1
● Responsive - it must react to its users ● Resilient - it must react to failure and stay
available● Elastic - it must react to variable load
conditions● Message-driven - it must react to inputs
Reactive - 2
Reactive - 3case class ParallelRetrievalExampleScala (val cacheRetriever: CacheRetriever, val dbRetriever: DBRetriever) {
def retrieveCustomer(id: Long) : Future[Customer] = {
val cacheFuture = Future { cacheRetriever.getCustomer(id) }
val dbFuture = Future { dbRetriever.getCustomer(id) }
Future.firstCompletedOf(List(cacheFuture, dbFuture))
}
}
public class ParallelRetrievalExample {
final CacheRetriever cacheRetriever;
final DBRetriever dbRetriever;
ParallelRetrievalExample(CacheRetriever cacheRetriever,
DBRetriever dbRetriever) {
this.cacheRetriever = cacheRetriever;
this.dbRetriever = dbRetriever;
}
public Object retrieveCustomer(final long id) {
final CompletableFuture<Object> cacheFuture = CompletableFuture.supplyAsync(() -> {return cacheRetriever.getCustomer(id); });
final CompletableFuture<Object> dbFuture = CompletableFuture.supplyAsync(() -> { return dbRetriever.getCustomer(id); });
return CompletableFuture.anyOf(cacheFuture, dbFuture);
}
}
Reactive - 4● def getProductInventoryByPostalCode( productSku: Long, postalCode: String): Future[(Long, Map[Long, Long])] = {
implicit val ec = ExecutionContext.fromExecutor(new ForkJoinPool())
implicit val timeout = 250 milliseconds
val localInventoryFuture = Future {
inventoryService.currentInventoryInWarehouse(
productSku, postalCode)
}
val overallInventoryFutureByWarehouse = Future {
inventoryService.currentInventoryOverallByWarehouse(
productSku)
}
for {
local <- localInventoryFuture
overall <- overallInventoryFutureByWarehouse
} yield (local, overall)
}
Reactive - 5case object Start
case class CounterMessage(counterValue: Int)
case class CounterTooLargeException(message: String) extends Exception(message)
class SupervisorActor extends Actor with ActorLogging {
override val supervisorStrategy = OneForOneStrategy() { case _: CounterTooLargeException => Restart }
val actor2 = context.actorOf(Props[SecondActor], "second-actor")
val actor1 = context.actorOf(Props(new FirstActor(actor2)), "first-actor")
def receive = { case Start => actor1 ! Start }
}
class AbstractCounterActor extends Actor with ActorLogging {
var counterValue = 0
def receive = { case _ => }
def counterReceive: Receive = LoggingReceive {
case CounterMessage(i) if i < 1000 => counterValue = i
log.info(s"Counter value: $counterValue")
sender ! CounterMessage(counterValue + 1)
case CounterMessage(i) => throw new CounterTooLargeException("Exceeded max value of counter!")
}
override def postRestart(reason: Throwable) = { context.parent ! Start }
}
Reactive - 6class FirstActor(secondActor: ActorRef) extends
AbstractCounterActor {
override def receive = LoggingReceive {
case Start =>
context.become(counterReceive)
log.info("Starting counter passing.")
secondActor ! CounterMessage(counterValue + 1)
}
}
class SecondActor() extends AbstractCounterActor {
override def receive = counterReceive
}
object Example extends App {
val system = ActorSystem("counter-supervision-example")
val supervisor = system.actorOf(Props[SupervisorActor])
supervisor ! Start
}
Reactive app – Spray + Slick + Akkaobject ExecutionContexts {
private val contextsSettings = ExecutionContextsSettings()
implicit val dbExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(contextsSettings.blockingThreadCount))
implicit val cpuIntensiveExecutionContext = ExecutionContext.fromExecutor(Executors.newWorkStealingPool(contextsSettings.cpuIntensiveThreadCount))
}
def accountSignUpRoute = {
respondWithMediaType(MediaTypes.`application/json`) {
(path(ApiRoot / "account") & post) {
entity(as[SignUp]) { signUp =>
onSuccess(accountService.createAccount(signUp)) {
case Left(failure) => complete(StatusCodes.Conflict,
"The email address you provided is already registered to another account")
case Right(acc) =>
respondWithHeader(Location(s"/$ApiRoot/account/${acc.id.get}")) {
setSession(SessionCookie(data = Map("id" -> acc.id.get.toString), path = Some("/"))) {
complete(StatusCodes.Created, acc)
}
}
}
}
}
}
}
Reactive app - 2
def createAccount(account: Account): Future[Either[AccountCreationFailure, Account]] =
Future {
db withTransaction { implicit session =>
opTimer.time {
// check that email address does not already exist
accountRepo.retrieveAccountByEmail(account.email) match {
case Some(acc) => Left(AccountCreationFailure(account.email)) // reject account exists
case _ =>
val createdAcc = accountRepo.createAccount(account)
pictureRepo.createPicture(Picture(url = "http://bit.ly/1y3A9HS", accountId = createdAcc.id.get))
Right(createdAcc)
}
}
}
}
Reactive app - 3 self.sendChatMessage = function() {
$.ajax("/api/chat",
{data: ko.toJSON({"message": self.newChatMessage})
, type: "post"
, contentType: "application/json"
, headers: { "sessionCsfrToken": $("#sessionCsfrToken").val() }
, error: function(jqXHR, textStatus, errorThrown) {
var json = JSON.parse(jqXHR.responseText);
if(json.redirect) {
window.location = json.redirect;
}
}
}
).always(function() { self.newChatMessage(''); })
};
// event source
self.makeEventSource = function() {
var s = new EventSource("/streaming/chat");
s.addEventListener("message", function(e) {
var parsed = JSON.parse(e.data);
var msg = new ChatMessage(parsed);
self.messages.push(msg);
}, false);
return s;
};
Reactive app -4
def chatRoute(implicit session: SessionCookie) = {
pathPrefix(ApiRoot) {
(path("chat") & post) {
chatTimer.time {
entity(as[ChatActor.ChatMessage]) { msg =>
chat ! msg
complete(StatusCodes.Accepted)
}
}
}
} ~ (get & pathPrefix("streaming")) {
respondAsEventStream {
path("chat") { ctx =>
chat ! ChatActor.AddListener(ctx)
}
}
}
}
Reactive app - 5class Chat(implicit inj: Injector) extends Actor with ActorLogging {
import Chat._
log.info("Starting chat actor.")
val watched = ArrayBuffer.empty[ActorRef]
def receive : Receive = {
case AddListener(ctx) =>
log.info(s"Adding SSE listener.")
val listener = context.actorOf(SSEActor.props(ctx))
context.watch(listener)
watched += listener
case msg @ ChatMessage(_) =>
log.info(s"Received chat message.")
watched.foreach(_ ! SSEActor.SSEEvent(event=Some("message"),data=List(msg.toJson.compactPrint)))
case Terminated(listener) =>
watched -= listener
}
}
Reactive app - 6private[chat]
class SSEActor(ctx:RequestContext) extends Actor with ActorLogging {
import SSEActor._
val comment = ":\n\n"
ctx.responder ! ChunkedResponseStart(HttpResponse(entity = comment))
context.setReceiveTimeout(20.seconds)
def receive: Receive = {
case evt @ SSEEvent(_,_,_,_) =>
log.debug(s"Sending SSE event: ${evt.toString}")
ctx.responder ! MessageChunk(evt.toString)
case ReceiveTimeout =>
ctx.responder ! MessageChunk(comment)
case SSEEnd =>
ctx.responder ! ChunkedMessageEnd
context.stop(self)
case SSEClose =>
// notify client to stop retrying
ctx.responder ! StatusCodes.NotFound
context.stop(self)
case ev: Http.ConnectionClosed =>
log.info(s"Stopping SSE stream, reason: signed out")
context.stop(self)
}
}
Persistence layer - 1private[account]
class AccountTable(tag: Tag) extends Table[Account](tag, "account") with Mappers {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[Name]("name", O.NotNull)
def email = column[Email]("email", O.NotNull)
def password = column[Password]("password", O.NotNull)
def activatedAt = column[LocalDateTime]("activated_at", O.Nullable)
def suspendedAt = column[LocalDateTime]("suspended_at", O.Nullable)
def * =
(id.?, name, email, password,
activatedAt.?, suspendedAt.?) <>
((Account.apply _).tupled, Account.unapply)
}
Persistence layer - 2private[account]
class Accounts extends MetricsInstrumented with Mappers {
implicit val dbExecContext = ExecutionContexts.dbExecutionContext
import storage.operationSuccessMapper
import scala.slick.jdbc.JdbcBackend.Session
private[this] val logger = Logger[this.type]
private val siteSettings = SiteSettings()
val accounts = TableQuery[AccountTable]
private val qRetrieveAccountByEmail = Compiled( (email: Column[Email]) =>
for { account <- accounts if account.email === email } yield account)
private val qRetrieveAccountPassword = Compiled( (id: Column[Long]) =>
for { account <- accounts if account.id === id } yield account.password )
private val qRetrieveAccount = Compiled( (id: Column[Long]) =>
for { account <- accounts if account.id === id } yield account ) // or accounts.filter(_.id === account.id.get)
def createAccount(account: Account)(implicit session: Session): Account = {
logger.info("account created")
val pw = Password.encrypt(siteSettings.encryptionLogRounds)(account.password)
// set random values
val createdAt = account.createdAt.getOrElse(LocalDateTime.now)
val currentLoginAt, lastLoginAt = LocalDateTime.now
val acc: Account = account.copy(password = pw, createdAt = Some(createdAt))
val id = accounts.returning(accounts.map(_.id)) += acc
account.copy(id = Some(id))
}
def retrieveAccount(id: Long)(implicit session: Session): Option[Account] = {
logger.info("account retrieved1")
qRetrieveAccount(id).firstOption
}
}
Akka persistence
● PersistentActor - a persistent, stateful actor. It is able to persist events to a journal and can react to them in a thread-safe manner. It can be used to implement both command as well as event sourced actors
● PersistentView - a view is a persistent, stateful actor that receives journaled messages that have been written by another persistent actor. A view itself does not journal new messages, instead, it updates internal state only from a persistent actor's replicated message stream.
● AtLeastOnceDelivery - sends messages with at-least-once delivery semantics to destinations, also in case of sender and receiver JVM crashes.
● AsyncWriteJournal - a journal stores the sequence of messages sent to a persistent actor. An application can control which messages are journaled and which are received by the persistent actor without being journaled. The storage backend of a journal is pluggable.
● Snapshot store - A snapshot store persists snapshots of a persistent actor's or a view's internal state. Snapshots are used for optimizing recovery times. The storage backend of a snapshot store is pluggable.
Akka persistence (PersistentActor)class ExamplePersistentActor extends PersistentActor {
override def persistenceId = "sample-id-1"
var state = ExampleState()
def updateState(event: Evt): Unit =
state = state.updated(event)
def numEvents =
state.size
val receiveRecover: Receive = {
case evt: Evt => updateState(evt)
case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot
}
val receiveCommand: Receive = {
case Cmd(data) =>
persist(Evt(s"${data}-${numEvents}"))(updateState)
persist(Evt(s"${data}-${numEvents + 1}")) { event =>
updateState(event)
context.system.eventStream.publish(event)
}
case "snap" => saveSnapshot(state)
case "print" => println(state)
}
}
Persistent actor failure example
class ExamplePersistentActor extends PersistentActor {
override def persistenceId = "sample-id-2"
var received: List[String] = Nil // state
def receiveCommand: Receive = {
case "print" => println(s"received ${received.reverse}")
case "boom" => throw new Exception("boom")
case payload: String =>
persist(payload) { p => received = p :: received }
}
def receiveRecover: Receive = {
case s: String => received = s :: received
}
}
Persistent view● class ExampleView extends PersistentView {
private var numReplicated = 0
override def persistenceId: String = "sample-id-4"
override def viewId = "sample-view-id-4"
def receive = {
case "snap" =>
println(s"view saving snapshot")
saveSnapshot(numReplicated)
case SnapshotOffer(metadata, snapshot: Int) =>
numReplicated = snapshot
println(s"view received snapshot offer ${snapshot} (metadata = ${metadata})")
case payload if isPersistent =>
numReplicated += 1
println(s"view replayed event ${payload} (num replicated = ${numReplicated})")
case SaveSnapshotSuccess(metadata) =>
println(s"view saved snapshot (metadata = ${metadata})")
case SaveSnapshotFailure(metadata, reason) =>
println(s"view snapshot failure (metadata = ${metadata}), caused by ${reason}")
case payload =>
println(s"view received other message ${payload}")
}
}
Lazy valsscala> object Demo {
val x = { println("initializing x"); "done" }
}
scala> Demo
initializing x
res3: Demo.type = Demo$@17469af
scala> Demo.x
res4: java.lang.String = done
scala> object Demo {
lazy val x = { println("initializing x"); "done" }
}
scala> Demo
res5: Demo.type = Demo$@11dda2d
scala> Demo.x
initializing x
res6: java.lang.String = done
ScalaTest – testing specs styles - 1import org.scalatest.FunSuite
class SetSuite extends FunSuite {
test("An empty Set should have size 0") {
assert(Set.empty.size == 0)
}
test("Invoking head on an empty Set should produce NoSuchElementException") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
import org.scalatest.FlatSpec
class SetSpec extends FlatSpec {
"An empty Set" should "have size 0" in {
assert(Set.empty.size == 0)
}
it should "produce NoSuchElementException when head is invoked" in {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
ScalaTest – testing specs styles - 2import org.scalatest.FunSpec
class SetSpec extends FunSpec {
describe("A Set") {
describe("when empty") {
it("should have size 0") {
assert(Set.empty.size == 0)
}
it("should produce NoSuchElementException when head is invoked") {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
import org.scalatest.WordSpec
class SetSpec extends WordSpec {
"A Set" when {
"empty" should {
"have size 0" in {
assert(Set.empty.size == 0)
}
"produce NoSuchElementException when head is invoked" in {
intercept[NoSuchElementException] {
Set.empty.head
}
}
}
}
}
Testing with mock objects
● ScalaMock● EasyMock● JMock● Mockito
Testing with mock objects - ScalaMock
class ExampleSpec extends FlatSpec with MockFactory {
...
// Function mocks
val m = mockFunction[Int, String]
m expects ( 42) returning "Forty two" once
// Proxy mocks
val m = mock[Turtle]
m expects 'setPosition withArgs(10.0, 10.0)
m expects 'forward withArgs (5.0)
m expects 'getPosition returning(15.0, 10.0)
m expects 'forward withArgs(*) once
m expects 'forward
m expects 'forward anyNumberOfTimes
m stubs 'forward
...
}
Property-based testingclass Fraction(n: Int, d: Int) {
require(d != 0)
require(d != Integer.MIN_VALUE)
require(n != Integer.MIN_VALUE)
val numer = if (d < 0) -1 * n else n
val denom = d.abs
override def toString = numer + " / " + denom
}
forAll { (n: Int, d: Int) =>
whenever (d != 0 && d != Integer.MIN_VALUE
&& n != Integer.MIN_VALUE) {
val f = new Fraction(n, d)
if (n < 0 && d < 0 || n > 0 && d > 0)
f.numer should be > 0
else if (n != 0)
f.numer should be < 0
else
f.numer should be === 0
f.denom should be > 0
}
}