Want to dive even deeper?

Take the course Stairway to Scala Advanced II by Bill Venners and Dick Wall and become an expert!
Stairway to Scala Advanced II
by Bill Venners , Dick Wall

Check it out!
You're watching a preview of this video, click the button on the left to puchase the full version from Devoxx 2012.

Unsung Heroes: Less Fashionable Patterns in Scala

Monads are the talk of the town, and everyone's doing them. In the virtual monad gold-rush, however, have we lost sight of some of the other useful tips, tricks and patterns that have got us to where we are. In this session I will examine a number of useful code snippets and ideas that I have used successfully on many projects, sometimes alongside monads, to great effect. There may be some useful corner cases you have overlooked, or perhaps just would like a refresher in some of the things you may have discovered but then forgotten.

This talk is suitable for Scala beginners but (I hope) will be of use to more experienced Scala developers as well, even if only as a gentle reminder about some of the tricks used, or perhaps in the effective combinations of some of them that have proven useful over time.


Published on
  • 2.777
  • 32
  • 1
  • 15
  • 0
  • Unsung Heroes Useful, Non-monadic Patterns
  • NoMonads? (NoSQL…) ● Not Only Monads ● Why? ● Why Not? ● Occam’s Razor Refresher
  • Monads ● Incredible, Cosmic Power
  • Why Not? ● Overkill ● Monads Don’t Mix ● Ease of Use ● Pragmatism
  • Occam’s Razor
  • The Three Laws of APIs An API Must Work An API Must be Simple An API Can be Clever, but not at the expense of the first two laws * These rules are also a good basis for all software development IMHO
  • The Mighty, Mighty Case Class // Stringly Typed: def lookUpEmpNo(first: String, last: String, branch: String): Int // Case Classes case class Person(first: String, last: String) case class Branch(name: String) case class Employee(person: Person, employeeNumber: Int) def lookUpEmpNo(person: Person, branch: Branch): Employee
  • And Don’t Forget Case Objects def lookupById(id: String, cache: Boolean, remote: Boolean): (String, String) case class EmployeeId(id: String) sealed abstract class IsCached(cached: Boolean) case object Cached extends IsCached(true) case object NotCached extends IsCached(false) sealed abstract class IsRemote(isRemote: Boolean) case object Local extends IsRemote(false) case object Remote extends IsRemote(true) def lookupById(id: EmployeeId, cache: IsCached, remote: IsRemote): Person
  • Loans def withPrintWriter(fileName: String)(fn: PrintWriter => Unit): Unit = { val printWriter = new PrintWriter(fileName) try { fn(printWriter) } finally printWriter.close() } withPrintWriter("hello.txt") { pw => pw.println("Hello, World!") pw.println("Goodbye Cruel World") }
  • Loans def withIterator[A](fileName: String)(fn: Iterator[String] => A): A={ val source = Source.fromFile(fileName) try { val iter = source.getLines() fn(iter) } finally source.close() } withIterator("hello.txt") { iter => iter.map(_.toUpperCase).toList } res5: List[String] = List(HELLO, WORLD!, GOODBYE CRUEL WORLD)
  • But... withIterator("hello.txt") { iter => iter.map(_.toUpperCase) } java.io.IOException: Bad file descriptor withIterator("hello.txt") { iter => iter } java.io.IOException: Bad file descriptor
  • Type Parameters case class DBQuery[T : JsonFormat : Manifest] (designDoc: String, view: String) object DBQuery { def apply[T : JsonFormat : Manifest](view: String) = { val designDoc = manifest[T].runtimeClass.getName DBQuery(designDoc, view) } }
  • Type Parameters def query[T : JsonFormat](dbQuery: DbQuery[T]): Seq[T] = { val results: Seq[String] = databaseQuery(dbQuery.designDoc, dbQuery.view) results.map(_.toJson.convertTo[T]) } // Because... val personQuery = DbQuery[Person]("allByName") val people = query(personQuery)
  • Generic Pattern Matching case class Doohickey[T](item: T, combinerFn: (T, T) => T) { def combineWith(other: Any): Option[T] = other match { case other: T => Some(combinerFn(item, other)) case _ => None } } warning: abstract type pattern T is unchecked since it is eliminated by erasure case other: T => Some(combinerFn(item, other))
  • Generic Pattern Matching scala> val strDoohickey = Doohickey[String]("Hello ", (x, y) => x+y) strDoohickey: Doohickey[String] = Doohickey(Hello ,<function2>) scala> strDoohickey.combineWith("Mum!") res12: Option[String] = Some(Hello Mum!) scala> strDoohickey.combineWith(3) java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  • Generic Pattern Matching case class Doohickey[T : ClassTag](item: T, combinerFn: (T,T) => T) { def combineWith(other: Any): Option[T] = other match { case other: T => Some(combinerFn(item, other)) case _ => None } } scala> strDoohickey.combineWith("Mum!") res15: Option[String] = Some(Hello Mum!) scala> strDoohickey.combineWith(8)
  • Generic Pattern Matching def isAListOf[T](x: Any)(implicit ct: ClassTag[T]) = x match { case Nil => "No, this is an empty list" case yes: List[T] if yes.head.getClass == ct.runtimeClass => s"Yes, $yes is a list of ${ct.runtimeClass.getName}" case _ => "No, this is a list of something else" } scala> isAListOf[String](List("hi", "there")) res19: String = Yes, List(hi, there) is a list of java.lang.String scala> isAListOf[String](List(1, 2)) res20: String = No, this is a list of something else
  • Types Again case class StringList(items: List[String]) case class IntList(items: List[Int]) def isAListOfStringOrInt(x: Any) = x match { case StringList(strings) => strings.map(_.toUpperCase).mkString case IntList(ints) => ints.sum.toString } scala> res23: scala> res25: isAListOfStringOrInt(IntList(List(1,2,3))) String = 6 isAListOfStringOrInt(StringList(List("hello", "mum"))) String = HELLOMUM
  • Immutable Builder case class Credentials(user: String, pass: String) case class HttpConnection(server: String, port: Int = 80, ssl: Boolean = false, creds: Option[Credentials] = None) HttpConnection("www.slashdot.org") HttpConnection(server = "localhost", port = 8443, ssl = true, creds = Some(Credentials(user = "dick", pass = "s3curep@ssw0rd")))
  • Immutable Builder case class DefinedSNP(name: String, location: Int, alleles: Set[Char]) case class NamedLocatedSNP(name: String, location: Int) { def alleles(alleles: String) = DefinedSNP(name, location, alleles.toSet) } case class NamedSNP(name: String) { def location(location: Int) = NamedLocatedSNP(name, location) } case object SNP { def named(name: String) = NamedSNP(name) } val theSnp = SNP named "HLA23*" location 1211243 alleles "ATG" theSnp: DefinedSNP = DefinedSNP(HLA23*,1211243,Set(A, T, G))
  • Type Classes import spray.json._ import DefaultJsonProtocol._ case class Color(name: String, red: Int, green: Int, blue: Int) implicit val colorFormat = jsonFormat4(Color) val color = Color("CadetBlue", 95, 158, 160) def printJson[JF : JsonFormat](json: JF): Unit = { val jsonFormat = implicitly[JsonFormat[JF]] // or use implicit parameter println (jsonFormat.write(json)) } printJson(color) {"name":"CadetBlue","red":95,"green":158,"blue":160}
  • State Machine Builders case class DBQuery(designDoc: String, view: String, params: Map[String, JsValue]) { def addParam[JS : JsonWriter](key: String, value: JS) = { val newParams = this.params + (key -> value.toJson) copy(params = newParams) } def reduce(reduce: Boolean): DBQuery = if (reduce) { val newParams = ((params - "group") - "include_docs") + ("reduce" -> reduce.toJson) copy(params = newParams) } else addParam("reduce", reduce) def startKey[JS : JsonWriter](startKey: JS) def endKey[JS : JsonWriter](endKey: JS) def key[JS : JsonWriter](key: JS) def keys[JS : JsonWriter](keys: Seq[JS]) //... } = = = = addParam("startkey", startKey) addParam("endkey", endKey) addParam("key", key) addParam("keys", keys.map(_.toJson).toList)
  • Case with Paired Type Class Companion sealed abstract class AbstractDBObject[O : Manifest] extends JsonFormat[O] { val typeManifest = manifest[O] } def getJson[T : AbstractDBObject](item: T): JsValue = { val dbObjCompanion = implicitly[AbstractDBObject[T]] println("Item manifest " + dbObjCompanion.typeManifest) dbObjCompanion.write(item) }
  • Case with Paired Type Class Companion abstract class DBObject2[T1 : JsonFormat, T2 : JsonFormat, O extends AbstractDBObject[O]{ : Manifest] def apply(v1:T1,v2:T2):O def unapply(o:O):Option[(T1,T2)] implicit val jsonFormat = this def write(o: O): JsValue = { unapply(o) match { case Some((v1, v2)) => JsArray(List(v1.toJson, v2.toJson)) } } def read(value: JsValue): O = ??? // implement here in terms of apply }
  • Case with Paired Type Class Companion case class Person(name: String, age: Int) object Person extends DBObject2[String, Int, Person] def getJson[T : AbstractDBObject](item: T): JsValue = { val dbObjCompanion = implicitly[AbstractDBObject[T]] println("Item manifest " + dbObjCompanion.typeManifest) dbObjCompanion.write(item) } scala> val person = Person("Harry Potter", 27) person: Person = Person(Harry Potter,27) scala> getJson(person) Item manifest $line20.$read$$iw$$iw$$iw$$iw$$iw$$iw$Person res0: spray.json.JsValue = ["Harry Potter",27]
  • Tagged Types type Tagged[U] = { type Tag = U } type @@[T, U] = T with Tagged[U] sealed trait KiloGram def KiloGram[A](a: A): A @@ KiloGram = a.asInstanceOf[A @@ KiloGram] val mass = KiloGram(20.0) mass: @@[Double,KiloGram] = 20.0 2 * mass res1: Double = 40.0
  • Constructor Function Loans class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule with Serializable { lazy val bindings = { val module = new Object with MutableBindingModule fn(module) module.freeze().fixed.bindings } } object NewBindingModule { def newBindingModule(fn: MutableBindingModule => Unit): BindingModule = new NewBindingModule(fn) }
  • Constructor Function Loans implicit val bindingModule = newBindingModule { module => import module._ bind [DBLookup] toProvider { module => new MySQLLookup(module) } bind [WebService] to newInstanceOf [RealWebService] bind [Int] idBy 'maxPoolSize toSingle 10 bind [QueryService] toSingle { new SlowInitQueryService } }
  • Parfait trait Db { def name: String } class MySQLDb extends Db { val name = "MySQL" } class PostgresDb extends Db { val name = "Postgres" }
  • Parfait trait WeatherWS { def currentTemp: Double } class ForecastIOWeatherWS extends WeatherWS { def currentTemp = 101.5 } class WeatherDotComWeatherWS extends WeatherWS { def currentTemp = 99.7 }
  • Parfait trait DbConfig { val db: Db } trait WeatherWSConfig { val weather: WeatherWS } trait WholeSystemConfig extends DbConfig with WeatherWSConfig
  • Parfait class DbDoer(implicit config: DbConfig) { val myDbName = config.db.name def getDbName: String = myDbName } class WeatherDoer(implicit config: WeatherWSConfig) { def getCurrentTemp: Double = config.weather.currentTemp } trait TempDoubler { def db: Db // pure abstract - must be provided by implementing class def weather: WeatherWS // likewise // alternatively, def wholeSystemConfig: WholeSystemConfig lazy val getIt: String = s"${db.name} x ${weather.currentTemp * 2}" // still need care with trait init - use lazy }
  • Parfait class TotalSystem(systemName: String)(implicit val config: WholeSystemConfig) extends TempDoubler { val db = config.db val weather = config.weather val dbThing = new DbDoer // implicitly injected with the config for Db val wsThing = new WeatherDoer // ditto, but different config type - WeatherWS def getBoth: String = s"System is $systemName, Db is ${dbThing.getDbName} and temperature is ${wsThing.getCurrentTemp}" val doubleTempAndDB: String = getIt }
  • Parfait class DbWeatherTest extends FunSpec with Matchers { describe ("Using the implicit parameter injection approach") { it ("should work for MySQL and Forecast.io") { implicit object MySQLForecastConfig extends WholeSystemConfig { val db: Db = new MySQLDb val weather: WeatherWS = new ForecastIOWeatherWS } val system = new TotalSystem("Test system 1") system.getBoth should be ("System is Test system 1, Db is MySQL and temperature is 101.5") }
  • Parfait it ("should work for Postgres and Weather.com") { implicit object MySQLForecastConfig extends WholeSystemConfig { val db: Db = new PostgresDb val weather: WeatherWS = new WeatherDotComWeatherWS } val system = new TotalSystem("Test system 2") system.getBoth should be ("System is Test system 2, Db is Postgres and temperature is 99.7") system.doubleTempAndDB should be ("Postgres x 199.4") } } }
  • Q&A ● If there’s time...
  • Tagged Types type Tagged[U] = { type Tag = U } type @@[T, U] = T with Tagged[U] sealed trait KiloGram def KiloGram[A](a: A): A @@ KiloGram = a.asInstanceOf[A @@ KiloGram] val mass = KiloGram(20.0) mass: @@[Double,KiloGram] = 20.0 2 * mass res1: Double = 40.0