KEMBAR78
Scala best practices | PPTX
Scala best practices
Agoda 2017
Alexander Zaidel
Functional programming
Is a programming paradigm—a style of building the structure and elements of computer
programs—that treats computation as the evaluation of mathematical functions and avoids
changing-state and mutable data.
Referential transparency and purity
An expression e is referentially transparent if for all programs p, all occurrences of e in p
can be replaced by the result of evaluating e, without affecting the observable behavior
of p. A function f is pure if the expression f(x) is referentially transparent for all
referentially transparent x.
Example of pure function:
def fun(a: Int, b: Int): Int = a / b
Example of pure function:
def wrong(a: Int, b: Int): Int = a / b
def right(a: Int, b: Int): Try[Int] = Try(a / b)
apply
def getBookingInfo(): BookingInfo = ???
val bookingInfo = getBookingInfo()
MakeABooking(bookingInfo)
MakeABooking.apply(bookingInfo)
apply
object MakeABooking {
def apply(bookingInfo: BookingInfo): Booking = {
new Booking(bookingInfo)
}
}
Enumerations
object UserType extends Enumeration {
val NewUser, RecurringUser = Value
}
Enumerations
def someAction(userType: UserType): Unit = {
userType match {
case UserType.NewUser => action1
case UserType.RecurringUser => action2
}
}
Enumerations
def someAction(userType: UserType.Value): Unit = {
userType match {
case UserType.NewUser => action1
case UserType.RecurringUser => action2
}
}
object UserType extends Enumeration {
type UserType = Value
val NewUser, RecurringUser = Value
}
class vs case class
Hashcode
Equals,
Appy
Public access modifiers
Unapply
Immutability
Serializable
copy
case classes
case class User(name: String, birthDay: java.util.Date, country: String)
val ivan = User("Ivan", new java.util.Date(), "Thailand")
val pipat = ivan.copy(name = "Pipat")
println(ivan.name)
case classes unapply
def getUser(): User = ???
getUser match {
case User("Ivan", _, _) | User(_, _, "Thailand") => action1
case _ => action2
}
case classes immutability
val ivan = User("Ivan", new java.util.Date(), "Thailand")
ivan.birthDay.setTime(1L)
real cost
case class Person(name: String, lastName: String)
https://pastebin.com/WFbbnZ0Y
https://pastebin.com/20DVQ9QS
default values
def findUser(userName: String, birthday: Date = new Date(), country: String =
"Thailand"): User = ???
findUser("name")
findUser("name", country = "USA")
unapply
class Hotel(val name: String, val address: String, val country: String)
def getHotel(): Hotel = ???
getHotel() match {
case Hotel(_, "", "UK") => action1
case _ => action2
}
unapply
object Hotel {
def unapply(hotel: Hotel): Option[(String, String, String)] = {
Option((hotel.name, hotel.address, hotel.country))
}
}
Companion object
class Booking {
import Booking._
private val id: Long = DefaultId
private val created: Long = 0L
}
Companion object
object Booking {
def doSomething(booking: Booking): Unit = {
println(booking.created)
}
private val DefaultId = 0L
}
lazy val
class Service {
private def longRunningComputation(): Int = {
Thread.sleep(1000)
42
}
private lazy val k = longRunningComputation()
}
public class Service {
private int k; private volatile boolean bitmap$0;
private int longRunningComputation(){Thread.sleep(1000L);return 42;}
private int k$lzycompute(){
synchronized (this){
if (!this.bitmap$0){
this.k = longRunningComputation();this.bitmap$0 = true;
} return this.k; } }
private int k(){ return this.bitmap$0 ? this.k : k$lzycompute(); }
}
lazy val causing a deadlock
https://issues.scala-lang.org/browse/SI-5808
Access modifiers
equals vs ==
object Foo {
def getName(): String = null
getName().equals("")
}
equals vs ==
object Foo {
def getName(): String = null
getName() == ""
}
Option
val value: String = ???
Option(value).map(someValue => {
val tmp = someValue + 1
println(tmp)
})
Option
val value: String = ???
Option(value).map { someValue =>
val tmp = someValue + 1
println(tmp)
}
Option
case class User(age: Int, name: String, gender: Option[String])
val user = User(25, "Ivan", Some("male"))
user.gender match {
case Some(gender) => println("Gender: " + gender)
case None => println("Gender: not specified")
}
Option
def foo(gender: String): Unit = ???
val user = User(25, "Ivan", Some("male"))
if(user.gender.isDefined) {
foo(user.gender.get)
}
Option
user.gender match {
case Some(gender) => Some(gender.length)
case None => None
}
Option
def findUserFromDb(userId: Int): Option[User] = ???
val currentUser = findUserFromDb(userId)
val defaultUser = findUserFromDb(defaultUserId)
currentUser orElse defaultUser
Option
def getName(): String = null
Some(getName()).map { name =>
name.length
}
Option
def getName(): String = null
Option(getName()).map { name =>
name.length
}
Collections
def predicate: Boolean = ???
def foo(): List[Int] = ???
if(predicate) foo() else List()
List.empty, Map.empty, Array.empty
def predicate: Boolean = ???
def foo(): List[Int] = ???
if(predicate) foo() else List.empty
def foo(): List[Int] = List(1, 2, 3)
foo() match {
case b: List[String] => println("String")
case a: List[Int] => println("Int")
case c: List[AnyRef] => println("AnyRef")
case c: List[AnyVal] => println("AnyVal")
}
Collections
(1 to 400).filter(_ > 200).map(_.toString)
vs
(1 to 400).collect{
case a if(a > 200) => a.toString
}
Collections
seq.find(_ == x).isDefined
vs
seq.exists(_ == x)
seq.contains(x)
Don’t compute full length for length matching
seq.length > n
seq.length < n
seq.length == n
seq.length != n
seq.lengthCompare(n) > 0
seq.lengthCompare(n) < 0
seq.lengthCompare(n) == 0
seq.lengthCompare(n) != 0
Don’t rely on == to compare array contents
array1 == array2
vs
array1.sameElements(array2)
Don’t check index bounds explicitly
if (i < seq.length) Some(seq(i)) else None
vs
seq.lift(i)
Be careful with contains argument type
Seq(1, 2, 3).contains("1") // compilable
vs
Seq(1, 2, 3).contains(1)
Merge consecutive filter calls
seq.filter(p1).filter(p2)
vs
seq.filter(x => p1(x) && p2(x))
seq.filter(p1).headOption.map
vs
seq.collectFirst {
case (x) if predicate =>
}
tuples
case class User(name: String, hobby: String)
List(User("Ivan", "football"),
User("Pipat", "scala"),
User("Eugene", "scala")
).groupBy(_.hobby).map(users => users._2.size)
tuples
List(
User("Ivan", "football"),
User("Pipat", "scala"),
User("Eugene", "scala")
).groupBy(_.hobby).map { case (hobby, users) => users.size }
Future
def apply[T](body : => T)(implicit executor :ExecutionContext) : Future[T]
Future
def longRunningJob(): Int = {Thread.sleep(1000); 42}
Future(longRunningJob()).map { result =>
result + 100
}.recover { case NonFatal(e) => 0 }
Future
val myFuture = Future(longRunningJob())
myFuture.onComplete{
case Success(value) => value + 100
case Failure(t) => 0
}
Future
case class Hotel(id: Long)
val preCachedHotels: List[Hotel] = ???
def dbCall(id: Long): Future[Hotel] = ???
def findHotel(id: Long): Future[Hotel] = preCachedHotels.find(_.id == id).
map { user => Future(user) }.getOrElse(dbCall(id))
Future: successful
case class Hotel(id: Long)
val preCachedHotels: List[Hotel] = ???
def dbCall(id: Long): Future[Hotel] = ???
def findHotel(id: Long): Future[Hotel] = preCachedHotels.find(_.id == id).
map { user => Future.successful(user) }.getOrElse(dbCall(id))
Future
def callDB(id: Long): Future[User] = ???
def findUser(id: Long): Future[User] = {
if(id < 0) Future(new Exception("User can't contain negative id"))
else callDB(id)
}
Future: failed
def callDB(id: Long): Future[User] = ???
def findUser(id: Long): Future[User] = {
if(id < 0) Future.failed(new Exception("User can't contain negative id"))
else callDB(id)
}
Future.sequence
case class User(id: Long)
def getAllUserIds(): List[Long] = ???
def findUserById(id: Long): Future[User] = ???
val usersF: List[Future[User]] = getAllUserIds().map(findUserById)
val allUsersFetchedF: Future[List[User]] = Future.sequence(usersF)
for comprehension
val aOption = Some(5); val bOption = Some(6)
aOption.flatMap(a => bOption.map(b => a + b))
for {
a <- aOption
b <- bOption
} yield a + b
for comprehension and futures
for {
cityId <- someCalculation()
countryId <- someOtherCalculation()
district <- someDifferentCalculation()
} yield doSomethingWith(cityId, countryId, district)
val cityIdF = someCalculation()
val countryIdF = someOtherCalculation()
val districtF = someDifferentCalculation()
for {
cityId <- cityIdF
countryId <- countryIdF
district <- districtF
} yield doSomethingWith(cityId, countryId, district)
Implicits conversion
case class User(name: String, age: Int)
implicit def userToString(user: User): String = s"${user.name}${user.age}"
def giveMeString(arg: String): Unit = println(arg)
val IamString = User("string", 42)
giveMeString(IamString)
Implicit Parameter
case class Minerals(typeOfMineral: String)
implicit val calcium = Minerals("calcium")
def needMoreMinerals(implicit minerals: Minerals): Unit =
println(minerals.typeOfMineral)
needMoreMinerals
PIMP my library
implicit class StringPimp(value: String) {
def allStringsAreMine(): String = "I was enslaved"
}
println("Hello".allStringsAreMine())
Abstract methods in traits
trait Foo { def foo() }
trait M extends Foo{abstract override def foo() { println("M"); super.foo() } }
class FooImpl1 extends Foo { override def foo() { println("Impl") } }
class FooImpl2 extends FooImpl1 with M
new FooImpl2().foo()
Sealed classes
def getMyOption[T](): MyOption[T] =
???
getMyOption() match {
case MyEmpty => action1
case MySome(a) => action1
}
sealed abstract class
MyOption[T]
case object MyEmpty extends
MyOption[Nothing]
case class MySome[T](t: T)
extends MyOption[T]
case class MyAnotherState[T](t: T)
extends MyOption[T]
Resources
Scala for the Impatient
https://pavelfatin.com/scala-collections-tips-and-tricks/
http://danielwestheide.com/scala/neophytes.html
Programming in Scala, 3rd Edition
Q & A

Scala best practices

  • 1.
    Scala best practices Agoda2017 Alexander Zaidel
  • 2.
    Functional programming Is aprogramming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
  • 3.
    Referential transparency andpurity An expression e is referentially transparent if for all programs p, all occurrences of e in p can be replaced by the result of evaluating e, without affecting the observable behavior of p. A function f is pure if the expression f(x) is referentially transparent for all referentially transparent x.
  • 4.
    Example of purefunction: def fun(a: Int, b: Int): Int = a / b
  • 5.
    Example of purefunction: def wrong(a: Int, b: Int): Int = a / b def right(a: Int, b: Int): Try[Int] = Try(a / b)
  • 6.
    apply def getBookingInfo(): BookingInfo= ??? val bookingInfo = getBookingInfo() MakeABooking(bookingInfo) MakeABooking.apply(bookingInfo)
  • 7.
    apply object MakeABooking { defapply(bookingInfo: BookingInfo): Booking = { new Booking(bookingInfo) } }
  • 8.
    Enumerations object UserType extendsEnumeration { val NewUser, RecurringUser = Value }
  • 9.
    Enumerations def someAction(userType: UserType):Unit = { userType match { case UserType.NewUser => action1 case UserType.RecurringUser => action2 } }
  • 10.
    Enumerations def someAction(userType: UserType.Value):Unit = { userType match { case UserType.NewUser => action1 case UserType.RecurringUser => action2 } }
  • 11.
    object UserType extendsEnumeration { type UserType = Value val NewUser, RecurringUser = Value }
  • 12.
    class vs caseclass Hashcode Equals, Appy Public access modifiers Unapply Immutability Serializable copy
  • 13.
    case classes case classUser(name: String, birthDay: java.util.Date, country: String) val ivan = User("Ivan", new java.util.Date(), "Thailand") val pipat = ivan.copy(name = "Pipat") println(ivan.name)
  • 14.
    case classes unapply defgetUser(): User = ??? getUser match { case User("Ivan", _, _) | User(_, _, "Thailand") => action1 case _ => action2 }
  • 15.
    case classes immutability valivan = User("Ivan", new java.util.Date(), "Thailand") ivan.birthDay.setTime(1L)
  • 16.
    real cost case classPerson(name: String, lastName: String) https://pastebin.com/WFbbnZ0Y https://pastebin.com/20DVQ9QS
  • 17.
    default values def findUser(userName:String, birthday: Date = new Date(), country: String = "Thailand"): User = ??? findUser("name") findUser("name", country = "USA")
  • 18.
    unapply class Hotel(val name:String, val address: String, val country: String) def getHotel(): Hotel = ??? getHotel() match { case Hotel(_, "", "UK") => action1 case _ => action2 }
  • 19.
    unapply object Hotel { defunapply(hotel: Hotel): Option[(String, String, String)] = { Option((hotel.name, hotel.address, hotel.country)) } }
  • 20.
    Companion object class Booking{ import Booking._ private val id: Long = DefaultId private val created: Long = 0L }
  • 21.
    Companion object object Booking{ def doSomething(booking: Booking): Unit = { println(booking.created) } private val DefaultId = 0L }
  • 22.
    lazy val class Service{ private def longRunningComputation(): Int = { Thread.sleep(1000) 42 } private lazy val k = longRunningComputation() }
  • 23.
    public class Service{ private int k; private volatile boolean bitmap$0; private int longRunningComputation(){Thread.sleep(1000L);return 42;} private int k$lzycompute(){ synchronized (this){ if (!this.bitmap$0){ this.k = longRunningComputation();this.bitmap$0 = true; } return this.k; } } private int k(){ return this.bitmap$0 ? this.k : k$lzycompute(); } }
  • 24.
    lazy val causinga deadlock https://issues.scala-lang.org/browse/SI-5808
  • 25.
  • 26.
    equals vs == objectFoo { def getName(): String = null getName().equals("") }
  • 27.
    equals vs == objectFoo { def getName(): String = null getName() == "" }
  • 28.
    Option val value: String= ??? Option(value).map(someValue => { val tmp = someValue + 1 println(tmp) })
  • 29.
    Option val value: String= ??? Option(value).map { someValue => val tmp = someValue + 1 println(tmp) }
  • 30.
    Option case class User(age:Int, name: String, gender: Option[String]) val user = User(25, "Ivan", Some("male")) user.gender match { case Some(gender) => println("Gender: " + gender) case None => println("Gender: not specified") }
  • 31.
    Option def foo(gender: String):Unit = ??? val user = User(25, "Ivan", Some("male")) if(user.gender.isDefined) { foo(user.gender.get) }
  • 32.
    Option user.gender match { caseSome(gender) => Some(gender.length) case None => None }
  • 33.
    Option def findUserFromDb(userId: Int):Option[User] = ??? val currentUser = findUserFromDb(userId) val defaultUser = findUserFromDb(defaultUserId) currentUser orElse defaultUser
  • 34.
    Option def getName(): String= null Some(getName()).map { name => name.length }
  • 35.
    Option def getName(): String= null Option(getName()).map { name => name.length }
  • 36.
    Collections def predicate: Boolean= ??? def foo(): List[Int] = ??? if(predicate) foo() else List()
  • 37.
    List.empty, Map.empty, Array.empty defpredicate: Boolean = ??? def foo(): List[Int] = ??? if(predicate) foo() else List.empty
  • 38.
    def foo(): List[Int]= List(1, 2, 3) foo() match { case b: List[String] => println("String") case a: List[Int] => println("Int") case c: List[AnyRef] => println("AnyRef") case c: List[AnyVal] => println("AnyVal") }
  • 39.
    Collections (1 to 400).filter(_> 200).map(_.toString) vs (1 to 400).collect{ case a if(a > 200) => a.toString }
  • 40.
  • 41.
    Don’t compute fulllength for length matching seq.length > n seq.length < n seq.length == n seq.length != n seq.lengthCompare(n) > 0 seq.lengthCompare(n) < 0 seq.lengthCompare(n) == 0 seq.lengthCompare(n) != 0
  • 42.
    Don’t rely on== to compare array contents array1 == array2 vs array1.sameElements(array2)
  • 43.
    Don’t check indexbounds explicitly if (i < seq.length) Some(seq(i)) else None vs seq.lift(i)
  • 44.
    Be careful withcontains argument type Seq(1, 2, 3).contains("1") // compilable vs Seq(1, 2, 3).contains(1)
  • 45.
    Merge consecutive filtercalls seq.filter(p1).filter(p2) vs seq.filter(x => p1(x) && p2(x))
  • 46.
  • 47.
    tuples case class User(name:String, hobby: String) List(User("Ivan", "football"), User("Pipat", "scala"), User("Eugene", "scala") ).groupBy(_.hobby).map(users => users._2.size)
  • 48.
    tuples List( User("Ivan", "football"), User("Pipat", "scala"), User("Eugene","scala") ).groupBy(_.hobby).map { case (hobby, users) => users.size }
  • 49.
    Future def apply[T](body :=> T)(implicit executor :ExecutionContext) : Future[T]
  • 50.
    Future def longRunningJob(): Int= {Thread.sleep(1000); 42} Future(longRunningJob()).map { result => result + 100 }.recover { case NonFatal(e) => 0 }
  • 51.
    Future val myFuture =Future(longRunningJob()) myFuture.onComplete{ case Success(value) => value + 100 case Failure(t) => 0 }
  • 52.
    Future case class Hotel(id:Long) val preCachedHotels: List[Hotel] = ??? def dbCall(id: Long): Future[Hotel] = ??? def findHotel(id: Long): Future[Hotel] = preCachedHotels.find(_.id == id). map { user => Future(user) }.getOrElse(dbCall(id))
  • 53.
    Future: successful case classHotel(id: Long) val preCachedHotels: List[Hotel] = ??? def dbCall(id: Long): Future[Hotel] = ??? def findHotel(id: Long): Future[Hotel] = preCachedHotels.find(_.id == id). map { user => Future.successful(user) }.getOrElse(dbCall(id))
  • 54.
    Future def callDB(id: Long):Future[User] = ??? def findUser(id: Long): Future[User] = { if(id < 0) Future(new Exception("User can't contain negative id")) else callDB(id) }
  • 55.
    Future: failed def callDB(id:Long): Future[User] = ??? def findUser(id: Long): Future[User] = { if(id < 0) Future.failed(new Exception("User can't contain negative id")) else callDB(id) }
  • 56.
    Future.sequence case class User(id:Long) def getAllUserIds(): List[Long] = ??? def findUserById(id: Long): Future[User] = ??? val usersF: List[Future[User]] = getAllUserIds().map(findUserById) val allUsersFetchedF: Future[List[User]] = Future.sequence(usersF)
  • 57.
    for comprehension val aOption= Some(5); val bOption = Some(6) aOption.flatMap(a => bOption.map(b => a + b)) for { a <- aOption b <- bOption } yield a + b
  • 58.
    for comprehension andfutures for { cityId <- someCalculation() countryId <- someOtherCalculation() district <- someDifferentCalculation() } yield doSomethingWith(cityId, countryId, district)
  • 59.
    val cityIdF =someCalculation() val countryIdF = someOtherCalculation() val districtF = someDifferentCalculation() for { cityId <- cityIdF countryId <- countryIdF district <- districtF } yield doSomethingWith(cityId, countryId, district)
  • 60.
    Implicits conversion case classUser(name: String, age: Int) implicit def userToString(user: User): String = s"${user.name}${user.age}" def giveMeString(arg: String): Unit = println(arg) val IamString = User("string", 42) giveMeString(IamString)
  • 61.
    Implicit Parameter case classMinerals(typeOfMineral: String) implicit val calcium = Minerals("calcium") def needMoreMinerals(implicit minerals: Minerals): Unit = println(minerals.typeOfMineral) needMoreMinerals
  • 62.
    PIMP my library implicitclass StringPimp(value: String) { def allStringsAreMine(): String = "I was enslaved" } println("Hello".allStringsAreMine())
  • 63.
    Abstract methods intraits trait Foo { def foo() } trait M extends Foo{abstract override def foo() { println("M"); super.foo() } } class FooImpl1 extends Foo { override def foo() { println("Impl") } } class FooImpl2 extends FooImpl1 with M new FooImpl2().foo()
  • 64.
    Sealed classes def getMyOption[T]():MyOption[T] = ??? getMyOption() match { case MyEmpty => action1 case MySome(a) => action1 } sealed abstract class MyOption[T] case object MyEmpty extends MyOption[Nothing] case class MySome[T](t: T) extends MyOption[T] case class MyAnotherState[T](t: T) extends MyOption[T]
  • 65.
    Resources Scala for theImpatient https://pavelfatin.com/scala-collections-tips-and-tricks/ http://danielwestheide.com/scala/neophytes.html Programming in Scala, 3rd Edition
  • 66.