effective way to code in scala
TRANSCRIPT
Effective way to code in Scala
Satendra Kumar Software Consultant Knoldus Software LLP
Topics Covered
Pattern match
Pattern match vs if/else
var vs val
Try/catch
Loop vs recursion
Tuple usage
getOrElse vs fold
Future Composition
Scala ExecutionContext
Play ExecutionContext
Akka ExecutionContext
Pattern Match
trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b }
Pattern Match
trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b }
Calling from another class: calculate(Add(2,3)) // output => 5
Pattern Match
trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b }
//Calling from another class: case class IncrementByOne(number: Int) extends Arithmetic calculate(IncrementByOne(2)) // output => ?
Pattern Match
trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b }
//Calling from another class: case class IncrementByOne(number: Int) extends Arithmetic calculate(IncrementByOne(2)) // output => Match error
Pattern Match
sealed trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b }
Alway use sealed keyword to avoid match error.
Pattern match
sealed trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic case class IncrementByOne(number: Int) extends Arithmetic
val exprs = List(Add(2, 3), Subtract(5, 3), Multiply(2, 3))
exprs map { expr => expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b case IncrementByOne(a) => a + 1 } }
Pattern match
sealed trait Arithmetic case class Add(a: Int, b: Int) extends Arithmetic case class Subtract(a: Int, b: Int) extends Arithmetic case class Multiply(a: Int, b: Int) extends Arithmetic case class IncrementByOne(number: Int) extends Arithmetic
val exprs = List(Add(2, 3), Subtract(5, 3), Multiply(2, 3))
exprs map { expr => expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b case IncrementByOne(a) => a + 1 } }
Improvement ?
Pattern Match
val exprs = List(Add(2, 3), Subtract(5, 3), Multiply(2, 3))
exprs map { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b case IncrementByOne(a) => a + 1
}
Pattern Match
def calculate(expr: Arithmetic): Int = expr match { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b case IncrementByOne(a) => a + 1 case IncrementByTwo(a) => a + 2 case IncrementByThree(a) => a + 3 case IncrementByFour(a) => a + 4 case IncrementByFive(a) => a + 5 case IncrementBySix(a) => a + 6 case IncrementBySeven(a) => a + 7 case IncrementByEight(a) => a + 8
}
Cyclomatic complexity > 10
Pattern Match
def calculate(expr: Arithmetic): Int = binaryOperation.orElse(increment)(expr)
val binaryOperation: PartialFunction[Arithmetic, Int] = { case Add(a, b) => a + b case Subtract(a, b) => a + b case Multiply(a, b) => a + b
}
val increment: PartialFunction[Arithmetic, Int] = { case IncrementByOne(a) => a + 1 case IncrementByTwo(a) => a + 2 case IncrementByThree(a) => a + 3 case IncrementByFour(a) => a + 4 case IncrementByFive(a) => a + 5 case IncrementBySix(a) => a + 6 case IncrementBySeven(a) => a + 7 case IncrementByEight(a) => a + 8 }
Pattern Match
def method1(flag: Boolean):String = flag match { case true => "TRUE" case false => "FALSE" }
def method2(flag: Boolean): String = if (flag) "TRUE" else "FALSE"
public java.lang.String method1(boolean); Code: 0: iload_1 1: istore_2 2: iconst_1 3: iload_2 4: if_icmpne 13 7: ldc #9 9: astore_3 10: goto 21 13: iconst_0 14: iload_2 15: if_icmpne 23 18: ldc #11 20: astore_3 21: aload_3 22: areturn 23: new #13 26: dup 27: iload_2 28: invokestatic #19 31: invokespecial #23 34: athrow
public java.lang.String method2(boolean); Code: 0: iload_1 1: ifeq 9 4: ldc #9 6: goto 11 9: ldc #11 11: areturn
public java.lang.String method1(boolean); Code: 0: iload_1 1: istore_2 2: iconst_1 3: iload_2 4: if_icmpne 13 7: ldc #9 9: astore_3 10: goto 21 13: iconst_0 14: iload_2 15: if_icmpne 23 18: ldc #11 20: astore_3 21: aload_3 22: areturn 23: new #13 26: dup 27: iload_2 28: invokestatic #19 31: invokespecial #23 34: athrow
public java.lang.String method2(boolean); Code: 0: iload_1 1: ifeq 9 4: ldc #9 6: goto 11 9: ldc #11 11: areturn
Avoid pattern match on boolean value
If/else
def method(flag: Boolean): String = if (flag == true) "TRUE" else "FALSE"
What is the problem ?
If/else
//Incorrect def method(flag: Boolean): String = if (flag == true) "TRUE" else "FALSE"
//correct def method(flag: Boolean): String = if (flag) "TRUE" else "FALSE"
val vs var
Variable typeCollection typeExample
val vs var
Variable typeCollection typeExampleImmutable Immutable Best
val map = Map("name" -> "sky")
val vs var
Variable typeCollection typeExampleImmutable Immutable Best
MutableImmutableGood
val map = Map("name" -> "sky")
var map = Map("name" -> "sky")
val vs var
Variable typeCollection typeExampleImmutable Immutable Best
MutableImmutableGood
ImmutableMutableOk
val map = Map("name" -> "sky")
val map = mutable.Map("name" -> "sky")
var map = Map("name" -> "sky")
val vs var
Variable typeCollection typeExampleImmutable Immutable Best
MutableImmutableGood
ImmutableMutableOk
MutableMutableworst
val map = Map("name" -> "sky")
val map = mutable.Map("name" -> "sky")
var map = Map("name" -> "sky")
var map = mutable.Map("name" -> "sky")
val vs var
Variable typeCollection typeExampleImmutable Immutable Best
MutableImmutableGood
ImmutableMutableOk
MutableMutableworst
val map = Map("name" -> "sky")
val map = mutable.Map("name" -> "sky")
var map = Map("name" -> "sky")
var map = mutable.Map("name" -> "sky")
Never use combination of both mutable variable and mutable collection
Try/catch
try { doSomething() } catch { case th: Throwable => someHandler() }
Try/catch
try { doSomething() } catch { case th: Throwable => someHandler() }
MUST NOT catch Throwable when catching Exceptions
Try/catch
import scala.util.control.NonFatal try { doSomething() } catch { case NonFatal(ex) => someHandler() }
Try/catch
object NonFatal { /** * Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal */ def apply(t: Throwable): Boolean = t match { // VirtualMachineError includes OutOfMemoryError and other fatal errors case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable => false
case _ => true } /** * Returns Some(t) if NonFatal(t) == true, otherwise None */ def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None}
Loop vs recursion
def factorialUsingLoop(n: Int): Int = { var accumulator = 1 var i = 1 while (i acc case _ => fac(n - 1, n * acc) } fac(n, 1) }
Tuple usage
def wordCount(text: String): List[(String, Int)] = text.split(" +").groupBy(word => word) .map { word => (word._1, word._2.length) } .toList .sortBy(_._2) .reverse
Tuple usage
def wordCount(text: String): List[(String, Int)] = text.split(" +").groupBy(word => word) .map { word => (word._1, word._2.length) } .toList .sortBy(_._2) .reverse
def wordCount(text: String): List[(String, Int)] = text.split(" +").groupBy(identity) .map { case (word, frequency) => (word, frequency.length) } .toList .sortBy { case (_, count) => count } .reverse
// More Readable
Tuple usage
def wordCount(text: String): List[(String, Int)] = text.split(" +").groupBy(word => word) .map { word => (word._1, word._2.length) } .toList .sortBy(_._2) .reverse
def wordCount(text: String): List[(String, Int)] = text.split(" +").groupBy(identity) .map { case (word, frequency) => (word, frequency.length) } .toList .sortBy { case (_, count) => count } .reverse
// More Readable
Avoid _1 and _2 method for accessing tuple value
getOrElse vs fold
val optionalInt:Option[Int]=Some(1) val intValue= optionalInt.getOrElse("0")
getOrElse vs fold
val optionalInt:Option[Int]=Some(1) val intValue= optionalInt.getOrElse("0")
String value
getOrElse vs fold
val optionalInt:Option[Int]=Some(1) val intValue= optionalInt.getOrElse("0")
String value Type of intValue become Any val intValue= optionalInt.fold("0")(v =>v)
getOrElse vs fold
val optionalInt:Option[Int]=Some(1) val intValue= optionalInt.getOrElse("0")
String value Type of intValue become Any val intValue= optionalInt.fold("0")(v =>v)
Compilation Error
getOrElse vs fold
val optionalInt:Option[Int]=Some(1) val intValue= optionalInt.getOrElse("0")
String value Type of intValue become Any val intValue= optionalInt.fold(0)(v =>v)
getOrElse is not typesafe so use fold instead of getOrElse
head and get
val list =List[Int]().head
head and get
val list =List[Int]().head
Exception: head of empty list val list =List[Int]().headOption
val optionalInt: Option[Int] = None
val intValue = optionalInt.get
head and get
val list =List[Int]().head
Exception: head of empty list val list =List[Int]().headOption
val optionalInt: Option[Int] = None
val intValue = optionalInt.get
scala.None$.get val intValue = optionalInt.fold(0)(v=>v)
head and get
val list =List[Int]().head
Exception: head of empty list val list =List[Int]().headOption
val optionalInt: Option[Int] = None
val intValue = optionalInt.get
scala.None$.get val intValue = optionalInt.fold(0)(v=>v)
Alway Avoid head and get method
Loop vs recursion
def factorialUsingLoop(n: Int): Int = { var accumulator = 1 var i = 1 while (i acc case _ => fac(n - 1, n * acc) } fac(n, 1) }
Avoid looping if possible and use tail recursion
Future Composition
def composingFuture(): Future[Int] = { val firstFuture: Future[Int] = method1() val secondFuture: Future[Int] = firstFuture.flatMap { v => method2(v) } val thirdFuture: Future[Int] = secondFuture.flatMap { v => method3(v) } val fourthFuture: Future[Int] = method4() // no composition val result: Future[Int] = thirdFuture.flatMap { v => method5(v) } result }
def method1(): Future[Int] = ???
def method2(value: Int): Future[Int] = ???
def method3(value: Int): Future[Int] = ???
def method4(): Future[Int] = ???
def method5(value: Int): Future[Int] = ???
Future Composition
def composingFuture(): Future[Int] = { val firstFuture: Future[Int] = method1() val secondFuture: Future[Int] = firstFuture.flatMap { v => method2(v) } val thirdFuture: Future[Int] = secondFuture.flatMap { v => method3(v) } val fourthFuture: Future[Int] = method4() // no composition val result: Future[Int] = thirdFuture.flatMap { v => method5(v) } result }
def method1(): Future[Int] = ???
def method2(value: Int): Future[Int] = ???
def method3(value: Int): Future[Int] = ???
def method4(): Future[Int] = ???
def method5(value: Int): Future[Int] = ???
what is the problem ?
Future Composition
def composingFuture(): Future[Int] = { val firstFuture: Future[Int] = method1() val secondFuture: Future[Int] = firstFuture.flatMap { v => method2(v) } val thirdFuture: Future[Int] = secondFuture.flatMap { v => method3(v) } val fourthFuture: Future[Int] = method4() val result: Future[Int] = thirdFuture.flatMap { v => method5(v) } fourthFuture.flatMap{ _=> result}// composing fourth future value }
def method1(): Future[Int] = ???
def method2(value: Int): Future[Int] = ???
def method3(value: Int): Future[Int] = ???
def method4(): Future[Int] = ???
def method5(value: Int): Future[Int] = ???
Fixed Value for Future
import scala.concurrent.ExecutionContext.Implicits.global
def calculate(value: Option[Int]): Future[Int] = value match { case Some(v) => method(v) case None => Future(0) }
def method(value: Int): Future[Int] = ???
Fixed Value for Future
def calculate(value: Option[Int]): Future[Int] = value match { case Some(v) => method(v) case None => Future.successful(0) }
def method(value: Int): Future[Int] = ???
Alway use Future.successful for fixed value and Future.failed for exception
Scala ExecutionContext
ForkJoinPool
scala.concurrent.context.minThreads = 1
scala.concurrent.context.numThreads =x1 x1 means => Runtime.getRuntime.availableProcessors * 1
scala.concurrent.context.maxThreads ="x1
Avalable in scala.concurrent.ExecutionContext.Implicits.global
Scala ExecutionContext
ForkJoinPool
scala.concurrent.context.minThreads = 1
scala.concurrent.context.numThreads =x1 x1 means => Runtime.getRuntime.availableProcessors * 1
scala.concurrent.context.maxThreads ="x1
Avalable in scala.concurrent.ExecutionContext.Implicits.global
Don't use Scala ExecutionContext for IO operations
For heavy IO
import java.util.concurrent.Executors
object ExecutionContext { ///read from config or environment val CONCURRENCY_FACTOR = 3
object IO { /** * Responsible to handle all DB calls */ implicit lazy val dbOperations: concurrent.ExecutionContext = concurrent.ExecutionContext .fromExecutor( Executors.newFixedThreadPool( Runtime.getRuntime.availableProcessors() * CONCURRENCY_FACTOR ) ) } }
For light weight IO
import java.util.concurrent.Executors
object ExecutionContext {
object IO { /** * Responsible to handle all light weight IO */ implicit lazy val simpleOperations: concurrent.ExecutionContext = concurrent.ExecutionContext.fromExecutor(Executors.newCachedThreadPool()) }
}
Play ExecutionContext
Available in play.api.libs.concurrent.Execution.Implicits.defaultContext
default configuration for Plays thread pool:
akka { actor { default-dispatcher { fork-join-executor { parallelism-factor = 1.0
parallelism-max = 24
# Setting this to LIFO changes the fork-join-executor # to use a stack discipline for task scheduling. This usually # improves throughput at the cost of possibly increasing # latency and risking task starvation (which should be rare). task-peeking-mode = LIFO } } }}
Akka ExecutionContext
The default Akka configuration:akka { actor { default-dispatcher { fork-join-executor { parallelism-min = 8 parallelism-factor = 3.0 parallelism-max = 64 task-peeking-mode = "FIFO" } } }}
For light weight IO
For IO use thread-pool-executor.
For Example:
my-thread-pool-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
core-pool-size-min = 20
core-pool-size-factor = 20
core-pool-size-max = 100 }
throughput = 1}
For Heavy IO
For IO use thread-pool-executor.
For Example:
my-thread-pool-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
core-pool-size-min = 8
core-pool-size-factor = 3
core-pool-size-max = 16 }
throughput = 1}
Thump Rule
In Scala, use Scala ExecutionContext
In Play, use Play ExecutionContext
In Akka, use Akka ExecutionContext
In Akka, One ActorSystem per Application
References
http://doc.akka.io
https://www.playframework.com/documentation/2.5.x/ThreadPools#Understanding-Play-thread-pools
http://twitter.github.io/effectivescala
https://github.com/alexandru/scala-best-practices
Thanks