effective scala henrik engstrÖm software engineer - typesafe @h3nk3
TRANSCRIPT
✦ Typesafe Console Tech Lead
✦ Akka Honorary Team Member
✦ Programming Scala since 2010
$ whoami
✦ Consultant since 1998 - mainly Java
✦ Been with Typesafe since 2011
✦ Arsenal Supporter + Martial Artist
About Typesafe
✦ The Typesafe Platform
‣ Play, Akka, Scala, Scala IDE, Slick, SBT, etc.
✦ Subscription
✦ Training and Consulting
Agenda✦ Basic Stuff
✦ Object Orientation in Scala
✦ Implicits
✦ Types
✦ Collections
✦ Pattern Matching
✦ Functional Programming
// REPL = Read Eval Print Loop$ scala_home/bin/scalaWelcome to Scala version 2.10.0scala> println("Hello, world!")Hello, world!scala>
Use the REPL
// Put JAR files in scala_home/lib to get access$ scala_home/bin/scalaWelcome to Scala version 2.10.0scala> import com.x.y.z.MyClassscala> val instance = new MyClassscala> instance.myMethod
The REPL and JARs
// JAVAString result = null;if (z < 9) result = "<9" else result = ">=9";System.out.println("Result: " + result);// SCALAprintln("Result: " + if (z < 9) "<9" else ">=9"))
Expression vs Statement
// What type is variable quiz?var x = 1val quiz = while (x < 10) { println("X is: " + x) x += 1}
Statement Pop Quiz
✦ All expressions in Scala returns a type
// Don’t tell the computer how to fishimport scala.collection.mutable.{HashSet => MHS}def findPeopleInCity(c: String, ppl: Seq[People]): Set[People] = { val found = new MHS[People]() for (p <- ppl) for (a <- p.address) if (a.city == c) found.put(p) found}
No Fishing Instructions Please!
def findPeopleInCity(c: String, ppl: Seq[People]): Set[People] = { for { p <- ppl.toSet[People] a <- p.address if a.city == c } yield p}// SQL LIKE SYNTAX; FROM, WHERE, AND, SELECT
Instead - Just Order Fish
// 1) Mutable code leads to cloning // 2) Cloning leads to performance degradation// => Mutable code leads to worse performanceclass Footballer { private var cars = Array[Car]() def setCars(c: Array[Car]): Unit = cars = c.clone def getCars: Array[Car] = cars.clone}
Stay Immutable
// Safer code - use immutable collectionclass Footballer { private var cars = Vector.empty[Car] def setCars(c: Vector[Car]) = cars = c def getCars: Vector[Car] = cars}
Stay Immutable
// Case classes make the class immutablescala> case class Car(brand: String)scala> case class Footballer(name: String, team: String, cars: Vector[Car] = Vector.empty)scala> var jw = new Footballer("Jack Wilshire", "Arsenal")Footballer(Jack Wilshire, Arsenal, Vector())scala> jw = jw.copy(cars = Vector(Car("Porsche")))Footballer(Jack Wilshire, Arsenal, Vector(Car(Porsche)))
Case Classes ftw
Immutable Benefits✦ Simple equality
✦ Simple hashcode
✦ No need to lock
✦ No defensive copying
✦ Scala Case Classes
‣ Automatic equality + hashcode (Murmur)
‣ Lots of other goodies (e.g. copy)
// Sometimes local mutability makes senseimport scala.collection.mutable.ArrayBufferclass MyClass { def theData: Seq[Int] = { val buffer = new ArrayBuffer[Int]() populateData(buffer) buffer.toSeq }}
Local Mutability
// A classic game of nulldef auth(usr: String, pwd: String): Privileges = if (usr == null || usr.isEmpty || pwd == null || pwd.isEmpty || !validCredentials(usr, pwd)) withPrivileges(Anonymous) else privilegesFor(usr)
Use Option
def auth(usr: Option[String], pwd: Option[String]): Privileges = { val privileges: Option[Privileges] = { u <- usr p <- pwd if (!u.isEmpty && !p.isEmpty) if canAuthenticate(u, p) } yield privilegesFor(u) privileges getOrElse withPrivileges(Anonymous)}
Use Option
trait SquareShape { val width: Int val height: Int val area: Int = width * height}class Rect(w: Int, h: Int) extends SquaredShape { override val width = w override val height = h}scala> val r1 = new Rectangle(1, 314)scala> r1.heightres0: Int = 314scala> r1.areares1: Int = 0
val for abstract members
Scala Initialization Order✦ From the Scala specification
(section 5.1)
- http://www.scala-lang.org/docu/files/ScalaReference.pdf
‣ First, the superclass constructor is evaluated
‣ Then, all base classes in the template’s linearization ...
‣ Finally the statement sequence stats is evaluated
trait SquareShape { def width: Int def height: Int def area: Int = width * height}class Rect(w: Int, h: Int) extends SquaredShape { override val width = w override val height = h}// or even bettercase class Rect(width: Int, height: Int) extends SquaredShape
def is much better
// Non-trivial return types should always be// annotated!def convert(x: Int) = x match { case 1 => 1.toChar case 2 => true case z => z.toByte}def convert(x: Int): AnyVal = x match {
Annotate your APIs
Composition and Inheritance
✦ Prefer composition over inheritance
- easier to modify (e.g. DI)
✦ Composition can use inheritance in Scala
- leads to the famous cake pattern
trait UserRepoComponent { def userLocator: UserLocator def userUpdater: UserUpdater trait UserLocator { def findAll: Vector[User] } trait UserUpdater { def save(user: User) }}
Let’s bake a cake
trait JPAUserRepoComponent extends UserRepoComponent { def em: EntityManager def userLocator = new JPAUserLocator(em) def userUpdater = new JPAUserUpdater(em) class JPAUserLocator(em: EntityManager) extends UserLocator { def findAll: Vector[User] = em.createQuery("from User", classOf[User]).getResultList.toVector } class JPAUserUpdater(em: EntityManager) extends UserUpdater { def save(user: User) = em.persist(user) }}
Baking in process
trait UserServiceComponent { def userService: UserService trait UserService { def findAll: Vector[User] def save(user: User): Unit def checkStatusOf(user: User): String }}
Service Layer
trait DefaultUserServiceComponent extends UserServiceComponent { this: UserRepositoryComponent => def userService = new DefaultUserService class DefaultUserService extends UserService { def findAll = userLocator.findAll def save(user: User) = userUpdater.save(user) def checkStatus(user: User) = s"User $user seems okay to me" }}
Service Layer Implementation
object MyApplication extends Application { val compService = new DefaultUserServiceComponent with JPAUserRepositoryComponent { def em = Persistence.createEntityManagerFactory( "cakepattern").createEntityManager() } val service = compService.userService // use the service}
Use it
class MyTest extends WordSpec with MustMatchers with Mockito { trait MockedEntityManager { def em = mock[EntityManager] } "service" must { "return all users" in { val compService = new DefaultUserServiceComponent with JPAUserRepositoryComponent with MockedEntityManager // perform tests }}
Test it
What is it good for?
✦ Removes boilerplate code in a specific context
‣ compile time safety
‣ must be unambiguous though
trait AutoRepository { def find(regId: String)(implicit dbId: DBId): Option[Car] def findAll(country: String)(implicit dbId: DBId): Seq[Car]}class DefaultAutoRepository extends AutoRepository { def find(regId: String)(implicit dbId: DBId): Option[Car] = { // ... } def findAll(country: String)(implicit dbId: DBId): Seq[Car] = { // ... }}
Example
// Anti patternclass CarFinder { val dbId = DbId("Dealer1") val repo = new DefaultAutoRepository def getCar(regId: String): Option[Car] = repo.find(regId)(dbId) def listCars(country: String): Seq[Car] = repo.findAll(country)(dbId)}
Example continued
// Use implicits => much cleaner codeclass CarFinder { implicit val dbId = DbId("Dealer1") val repo = new DefaultAutoRepository def getCar(regId: String): Option[Car] = repo.find(regId) def listCars(country: String): Seq[Car] = repo.findAll(country)}
Example continued
Compiler workout✦ Implicit scope
‣ Lexical : current scope, explicit imports, wildcard imports
‣ Companions of parts of the type : companion of types, companion of types of arguments, outer objects of nested types
✦ Can be expensive in compile time - use with care
trait Logger { def log(msg: String) }object Logger { implicit object DefaultLogger extends Logger { def log(msg: String) = println("DL> " + msg) } def log(msg: String)(implicit logger: Logger) = { logger.log(msg) }}
Implicit Values
scala> Logger.log("a small test")DL> a small testscala> class MyLogger extends Logger { def log(msg: String) = println("ML:>> " + msg) }scala> implicit def myLogger = new MyLoggerscala> Logger.log("another test")ML:>> another test
Implicit Values
Implicits wisdom?
deech @deechDebugging #scala implicits islike trying to find the farter in a crowed room
// "describes generic interfaces using type // parameters such that the implementations can// be created for any type"trait Encodable[T] { def from(t: T): String def to(s: String): T}object Encodable { implicit object IntEncoder extends Encodable[Int] { def from(i: Int): String = "int" + i def to(s: String): Int = s.substring(s.indexOf("int")+3, s.length).toInt }}
a.k.a Type Classes
class MyHandler { def convert[T](t: T)(implicit enc: Encodable[T]): String = enc.from(t) def convert[T](s: String)(implicit enc: Encodable[T]): T = enc.to(s)}scala> val myHandler = new MyHandlerscala> myHandler.convert(12345)res0: String = int12345scala> myHandler.convert(res0)res1: Int = 12345
Example Usage
scala> myHandler.convert(12345L)<console>:15: error: could not find implicit value for parameter encoder: Encodable[Long]scala> implicit object LongEnc extends Encodable[Long] { def from(l: Long): String = "long" + l def to(s: String): Long = s.substring(s.indexOf("long")+4, s.length).toLong }scala> myHandler.convert(12345L)
Example continued
// It is absolutely *awesome*scala> val seq = Seq()scala> seq.++ ++: +: /: /:\ :+ :\ addString aggregate andThen apply applyOrElse
asInstanceOf canEqual collect collectFirst combinations companion compose contains containsSlice copyToArray copyToBuffer
corresponds count diff distinct drop dropRight dropWhile endsWith exists filter filterNot find
flatMap flatten fold foldLeft foldRight forall foreach genericBuilder groupBy grouped hasDefiniteSize head
headOption indexOf indexOfSlice indexWhere indices init inits intersect isDefinedAt isEmpty isInstanceOf
isTraversableAgain iterator last lastIndexOf lastIndexOfSlice lastIndexWhere lastOption length lengthCompare lift map max
maxBy min minBy mkString nonEmpty orElse padTo par partition patch permutations
prefixLength product reduce reduceLeft reduceLeftOption reduceOption reduceRight reduceRightOption repr reverse reverseIterator
reverseMap runWith sameElements scan scanLeft scanRight segmentLength seq size slice sliding sortBy
sortWith sorted span splitAt startsWith stringPrefix sum tail tails take takeRight takeWhile
to toArray toBuffer toIndexedSeq toIterable toIterator toList toMap toSeq toSet toStream toString
toTraversable toVector transpose union unzip unzip3 updated view withFilter zip zipAll zipWithIndex
Explore and learn the API
@scala.annotation.tailrecdef len[A](v: Vector[A], l: Int): Int = v match { case h :: t => len(t, l + 1) case Nil => l}scala> len(Vector("a","b","c","d")res0: Int = 4
FP Pattern Matching
def convertedAge(a: Animal): Int = a match { case Dog(name, age) => age * 7 case Human(_, age, _) => age case Walrus("Donny", age) => age / 10 case Walrus(name, age) if name == "Walter" => age case _ => 0}
Extracting and "instanceof"
Associated Cost
✦ Be aware that pattern matching is if - else if under the hood.
✦ Try to put the most common occurrences in the beginning to reduce the number of hops
Word of Advice
✦ Learn patterns of Functional Programming
✦ Awesome post on Functors, Applicatives and Monads
‣ http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
✦ See scalaz for more on FP in Scala
Value and Context
23
value
(+8)
function
31
2323
value & context
2323
Some[Int] None
Option
context
value
def dblFunc(values: Vector[Int]): Vector[Int] = values map { _ * 2 } scala> dblFunc(Vector(1,2,3,4))res0: Vector(2,4,6,8)
Functors in Scala
scala> Vector(1,2,3)res0: Vector[Int] = Vector(1,2,3)scala> List("A","B","C")res1: List[String] = List(A,B,C)scala> res0 zip res1res2: List[(Int,String)] = List((1,A),(2,B),(3,C))
Scala Applicative Example
import scala.language.higherKindstrait Monad[M[_]] { def pure[A](a: A): M[A] def bind[A,B](ma: M[A])(f: A => M[B]): M[B]}
Monad Definition
def even(number: Option[Int]): Option[Int] = for { n <- number if n % 2 == 0 } yield nscala> even(Some(22))res1: Option[Int] = Some(22)scala> even(Some(21))res2: Option[Int] = None
Scala Monad Example
To Recap✦ FUNCTOR
- apply a function to a wrapped value
✦ APPLICATIVE
- apply a wrapped function to a wrapped value
✦ MONAD
- apply a function that returns a wrapped value to a wrapped value