Scala: Beyond the basics

Christopher Batey (@chbatey)
Software Engineer on Akka Team

Akka logo

Open Source toolkit for building Concurrent, Distributed, Resilient Message-Driven applications on the JVM

Agenda:

  1. Programming language terminology
  2. Function composition instead of layers
  3. Abstracting over higher kinded types
  4. Compile time implicits vs runtime reflection

1.Programming Languages

Scala is a Hybrid Functional / OO language

Semantics vs Syntax

Unfamiliar syntax != complexity

Expressions

What does this evaluate to in Java?

if (x > 0) {
  true;
} else {
  false;
}

Expressions

boolean positive;
if (x > 0) {
  positive = true;
} else {
  positive = false;
}

Expressions

boolean shouldAct;
if (x > 0) {
  shouldAct = true;
}

Expressions

What does this evaluate to in Scala?

if (x > 0) {
  true
} else {
  false
}

Expressions

What does this evaluate to in Scala?

val positive = if (x > 0) {
  true
} else {
  false
}

Expressions

val positive = if (x > 0) true else false

Statements have side effects

Expressions evaluate to values

Immutability

Referential transparency

val x: Int = ???
val y = x * x
val x = 4
val x = { println("haha"); 4}

Function composition

http://batey.info/fs-function-composition

type WebRequest = HttpRequest => HttpResponse
case class Person(name: String)
case class Customer(customerNumber: Int, p: Person)
trait Serialiser {
  def serialise(c: Customer): HttpResponse
  def deserialie(input: HttpRequest): Person
}
class WebController(
                     service: ApplicationService,
                     serialiser: Serialiser) {
  def register(p: HttpRequest): HttpResponse = {
    val person = serialiser.deserialie(p)
    val c = service.registerCustomer(person)
    serialiser.serialise(c)
  }
}
class ApplicationService(dataStore: CustomerRepo) {

  def registerCustomer(p: Person): Customer = {
    // validate them?
    // Generate a unique customer number?
    val c = Customer(1, p)
    dataStore.saveCustomer(c)
    c
  }
}
trait CustomerRepo {
  def saveCustomer(p: Customer): Unit
}

Do it yourself wiring

Three language features to avoid this:

  • Function Composition
  • Currying
  • Partial Application
public static int add(int x, int y) {
  return x + y;
}
public static int add10(int x) {
  return add(x, 10);
}
public static int multiply(int x, int y) {
  return x * y;
}
public static int multiplyBy10(int x) {
  return multiply(x, 10);
}
public static int addTenThenMultiplyByTen(int x) {
  return multiplyBy10(add10(x));
}
def add(x: Int, y: Int) = x + y
def add10(x: Int) = add(x, 10)
def addCurried(x: Int)(y: Int): Int = x + y
val whatTypeAmI = addCurried(10) _
val whatTypeAmI: Int => Int = addCurried(10)
val add10Curried = (add _).curried(10)
def add(x: Int)(y: Int): Int = x + y
def multiply(x: Int)(y: Int): Int = x * y
val add10multiply10Compose: Int => Int =
    multiply(10) _ compose add(10)
val add10multiply10 = add(10) _ andThen multiply(10)

Currying

  • Each function a single argument
  • Enables partial application

Partial application

  • Provide a subset of the parameters to get a new function

Function composition

f: String -> Int

g: Int -> Banana

g compose f

f andThen g

String -> Banana

type WebRequest = HttpRequest => HttpResponse
case class Person(name: String)
case class Customer(customerNumber: Int, p: Person)
trait Serialiser {
  def serialise(c: Customer): HttpResponse
  def deserialie(input: HttpRequest): Person
}
class WebController(
                     service: ApplicationService,
                     serialiser: Serialiser) {
  def register(p: HttpRequest): HttpResponse = {
    val person = serialiser.deserialie(p)
    val c = service.registerCustomer(person)
    serialiser.serialise(c)
  }
}
class ApplicationService(dataStore: CustomerRepo) {

  def registerCustomer(p: Person): Customer = {
    // validate them?
    // Generate a unique customer number?
    val c = Customer(1, p)
    dataStore.saveCustomer(c)
    c
  }
}
trait CustomerRepo {
  def saveCustomer(p: Customer): Unit
}
type WebRequest = HttpRequest => HttpResponse
val deSerialisePerson: HttpRequest => Person = ???
val createCustomer: Person => Customer = ???
val saveCustomer: Customer => Customer = ???
val serialiseCustomer: Customer => HttpResponse = ???
val registerCustomer: WebRequest =
  deSerialisePerson andThen
    createCustomer andThen
    saveCustomer andThen
    serialiseCustomer
type WebRequest = HttpRequest => HttpResponse

Currying + Partial application to the rescue

val databaseConnection = new DatabaseConnection
object DataAccess {
  def saveCustomer(databaseConnection: DatabaseConnection)
                  (c: Customer): Customer = ???
}
val saveCustomer: Customer => Customer =
  DataAccess.saveCustomer(dbConnection)
val registerCustomer: WebRequest =
  deSerialisePerson andThen
    createCustomer andThen
    saveCustomer andThen
    serialiseCustomer
type WebRequest = HttpRequest => HttpResponse
class DataAccessClass(databaseConnection: DatabaseConnection) {
  def saveCustomer(c: Customer): Customer = ???
}
val dac = new DataAccessClass(dbConnection)
val saveCustomer: Customer => Customer = dac.saveCustomer
object DatabaseAccessImplicit {
  def saveCustomer(c: Customer)
                  (implicit db: DatabaseConnection): Customer = ???
}
val saveCustomer: Customer => Customer =
  DatabaseAccessImplicit.saveCustomer
implicit val dbConnection = new DatabaseConnection

Section summary

  • Techniques
    • Function Composition
    • Currying
    • Partial Application
  • Benefits
    • Reduced need for mocking
    • Reduced need for a DI framework

Higher kinded types

String
int
Person
Customer

vs

List<T>
Iterable<T>
List<T> :: T -> List<T>
String -> List<String>

List is a type level function takes a type and produces a concrete type

Map<K, V>

Map takes two types and produces a concrete type

Abstracting over higher kinded types

Abstract over a type:

List<A>

Abstract over a type constructor:

T<Int>

Why not both?

T<A>

x

Motivation

type WebRequest = HttpRequest => Future[HttpResponse]
type WebRequest2 = HttpRequest => Either[Error, HttpResponse]
type WebRequest = HttpRequest => Future[HttpResponse]
def serialiseCustomer: Customer => HttpResponse = ???
def deSerialisePerson: HttpRequest => Person = ???
def createCustomer: Person => Future[Customer] = ???
def saveCustomer: Customer => Future[Customer] = ???
val fstStep: HttpRequest => Future[Customer] =
  deSerialisePerson andThen
    createCustomer
def flatMap(f: A => Future[B]): Future[B]
def map(f: A => B): Future[B]
val customerSaved: HttpRequest => Future[Customer] = hr =>
  fstStep(hr).flatMap(saveCustomer)
val fullRequest: HttpRequest => Future[HttpResponse] = hr =>
  customerSaved(hr).map(serialiseCustomer)
f: A => Future[B]
g: B => Future[C]

g superCompose f : A => Future[C]

http://eed3si9n.com/herding-cats/composing-monadic-functions.html

https://underscore.io/books/scala-with-cats/

Ready to do all this again with Either?

abstract class Abstracted[F[_] : Mappable] {
  def serialiseCustomer: Customer => HttpResponse = ???
  def deSerialisePerson: HttpRequest => Person = ???
  def createCustomer: Person => F[Customer] = ???
  def saveCustomer: Customer => F[Customer] = ???
}
trait Mappable[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
val first: HttpRequest => F[Customer] =
  deSerialisePerson andThen
    createCustomer
val fullRequest: HttpRequest => F[HttpResponse] = hr =>
  first(hr)
    .flatMap(saveCustomer)
    .map(serialiseCustomer)
val withAFuture = new Abstracted[Future] {}
val whatTypeIsThis: HttpRequest => Future[HttpResponse] =
  withAFuture.fullRequest

How useful is this?

  • Ratpack Promise
  • Hystrix: RxJava Observable
  • Cassandra database driver: Guava Listenable Future
  • Java8’s Completable Future

Implicits

Implicits

Many sensible uses of implicits replace reflection Reflection is more implicit than implicits!!

Implicit type conversions

case class Cat(name: String)
case class Dog(name: String, status: String)
def cleanDog(dog: Dog): Dog =
  dog.copy(status = "clean with hose pipe")
val ruby = Cat("Ruby")

Implicit type conversions

case class Cat(name: String)
case class Dog(name: String, status: String)
def cleanDog(dog: Dog): Dog =
  dog.copy(status = "clean with hose pipe")
val ruby = Cat("Ruby")
cleanDog(ruby)
implicit def catToDog(cat: Cat): Dog = Dog(cat.name, "clean")

Principled type conversions

trait Mappable[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
implicit def futureMappable = new Mappable[Future] {
  def map[A, B](fa: Future[A])(f: A => B): Future[B] =
    fa.map(f)
  def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
    fa.flatMap(f)
}
implicit def completableFutureMappable= new Mappable[CompletableFuture] {
  def map[A, B](fa: CompletableFuture[A])(f: A => B): CompletableFuture[B] =
    fa.thenApply(a => f(a))
  def flatMap[A, B](fa: CompletableFuture[A])(f: A => CompletableFuture[B]): CompletableFuture[B] =
    fa.thenCompose(a => f(a))
}

Summary

  1. Programming Language
  2. Function composition instead of layers
  3. Abstracting over higher kinded types
  4. Compile time implicits vs runtime reflection

Guidelines for a successful Scala project

  • Be prepared to be a beginner
  • Pick a programming paradigm
  • Pick a style guideline

Thanks for listening!

Happy hAkking!

Slides & Code
github.com/chbatey/scala-basics
Akka
akka.io, developer.lightbend.com/start
Tweet
@chbatey

Implicit evidence

trait Request[Req, Resp] {
  def invoke(in: Req): Future[Resp]
}
val liftedCall: Request[String, Int] =
  RequestBuilder[String, Int]()
    .withTimeout(10.seconds)
    .withCircuitBreaker()
    .build()
liftedCall.invoke("go go gadget go")
val noParamRequest = new Request[Unit, String] {
  def invoke(in: Unit): Future[String] = ???
}
trait NoParamRequest[Resp] {
  def invoke(): Future[Resp]
}
trait Request[Req, Resp] {
  def invoke(in: Req): Future[Resp]
  def invoke()(implicit ev: Req =:= Unit): Future[Resp]
}
liftedCall.invoke()

Cannot prove that String =:= Unit. liftedCall.invoke()

Scala