Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Effective way to code in Scala
1. Effective way to code in ScalaEffective way to code in Scala
Satendra Kumar
Software Consultant
Knoldus Software LLP
2. 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
3. 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
}
4. 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
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 => ?
6. 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
7. 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.
8. 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
}
}
9. 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 ?
10. 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
}
11. 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
12. 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
}
13. 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"
19. val vs var
Variable type Collection type Example
Immutable Immutable Best
val map = Map("name" -> "sky")
20. val vs var
Variable type Collection type Example
Immutable Immutable Best
Mutable Immutable Good
val map = Map("name" -> "sky")
var map = Map("name" -> "sky")
21. val vs var
Variable type Collection type Example
Immutable Immutable Best
Mutable Immutable Good
Immutable Mutable Ok
val map = Map("name" -> "sky")
val map = mutable.Map("name" -> "sky")
var map = Map("name" -> "sky")
22. val vs var
Variable type Collection type Example
Immutable Immutable Best
Mutable Immutable Good
Immutable Mutable Ok
Mutable Mutable worst
val map = Map("name" -> "sky")
val map = mutable.Map("name" -> "sky")
var map = Map("name" -> "sky")
var map = mutable.Map("name" -> "sky")
23. val vs var
Variable type Collection type Example
Immutable Immutable Best
Mutable Immutable Good
Immutable Mutable Ok
Mutable Mutable worst
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
27. 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
}
28. Loop vs recursion
def factorialUsingLoop(n: Int): Int = {
var accumulator = 1
var i = 1
while (i <= n) {
accumulator = i * accumulator
i += 1
}
accumulator
}
def factorialUsingRecursion(n: Int): Int = {
@tailrec
def fac(n: Int, acc: Int): Int = n match {
case _ if n == 1 => acc
case _ => fac(n - 1, n * acc)
}
fac(n, 1)
}
31. 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
32. getOrElse vs fold
val optionalInt:Option[Int]=Some(1)
val intValue= optionalInt.getOrElse("0")
33. getOrElse vs fold
val optionalInt:Option[Int]=Some(1)
val intValue= optionalInt.getOrElse("0")
String value
34. 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)
35. 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
36. 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
38. 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
39. 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)
40. 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
41. Loop vs recursion
def factorialUsingLoop(n: Int): Int = {
var accumulator = 1
var i = 1
while (i <= n) {
accumulator = i * accumulator
i += 1
}
accumulator
}
def factorialUsingRecursion(n: Int): Int = {
@tailrec
def fac(n: Int, acc: Int): Int = n match {
case _ if n == 1 => acc
case _ => fac(n - 1, n * acc)
}
fac(n, 1)
}
Avoid looping if possible and use tail recursion
42. 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] = ???
43. 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 ?
44. 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] = ???
45. 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] = ???
46. 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
48. 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
49. 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
)
)
}
}
50. 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())
}
}
51. Play ExecutionContext
● Available in play.api.libs.concurrent.Execution.Implicits.defaultContext
● default configuration for Play’s 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
}
}
}
}
53. 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
}
54. 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
}
55. Thump Rule
● In Scala, use Scala ExecutionContext
● In Play, use Play ExecutionContext
● In Akka, use Akka ExecutionContext
● In Akka, One ActorSystem per Application