effective way to code in scala

Download Effective way to code in Scala

If you can't read please download the document

Upload: knoldus-software-llp

Post on 15-Feb-2017

2.407 views

Category:

Software


1 download

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