effective scala henrik engstrÖm software engineer - typesafe @h3nk3

68
Effective Scala HENRIK ENGSTRÖM SOFTWARE ENGINEER - TYPESAFE @h3nk3

Upload: lorena-shelton

Post on 02-Jan-2016

223 views

Category:

Documents


0 download

TRANSCRIPT

Effective ScalaHENRIK ENGSTRÖM

SOFTWARE ENGINEER - TYPESAFE@h3nk3

✦ 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

Basic Stuff

// 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

REPL 2013✦ IDE Worksheet

- Scala IDE : awesome

- IntelliJ : okay

// 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

Object Orientation

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

Implicits

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

Type Traits

// "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

Collections

Collections Overview

Immutable Collections

Mutable Collections

// 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

Message to Java Developers

✦ Use Vector not List

‣ it is faster

‣ it is more memory efficient

Pattern Matching

@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

Functional Programming

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

Functor

2323 (+8) not applicable!

2323

23 (+8) 31

3131

Some[Int] Some[Int]X (+8) X

NoneNone

trait Functor[F[_]] { def fmap[A,B](fa: F[A], f: A => B): F[B]}

Functor Definition

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

Applicatives

2323

Some[Int]

(+8(+8))

Some[f(x)]

23(+8)

3131

Some[Int]

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

Monad

2323

Some[Int]

23

isEven

2424

24

4848

24

2424

Some[Int]

isEven

*2None

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

Recommended Scala Books

EOF@h3nk3