Effective way to code in Scala

download Effective way to code in Scala

If you can't read please download the document

Transcript of Effective way to code in Scala

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