Scala - Just Do It!

(Functionally)

Colt Frederickson

@coltfred

Who am I?

  • Working on a small "Big Data" team at Oracle
  • Writing Scala since 2011
  • Functional Programming Convert
  • Haskell Dabbler

Ask Questions!

Scala Syntax Guide

val x = 1

var y = 100
y = y + 1 //101

val z: Int = y

Scala Syntax Guide

case class Tuple2[A,B](first: A, second: B)

val t: Tuple2[Int,String] = Tuple2(2, "hello")

//I could just say
val tt = Tuple2(2, "hello")

def plus1ToFirst[A](t: Tuple2[Int,A]) = t.copy(first = t.first + 1)

Algebraic Data Type

sealed trait Option[+A]

case class Some[A](a: A) extends Option[A]

case object None extends Option[Nothing]

Polymorphism

class HttpConnection {
  def fetch(url:String): String
  def close: Unit
}

class HBaseTable extends java.io.Closeable {
  def get(row: String, col: String): String
  def close: Unit 
}

//I want to be able to write things like the following.

val result = using(new HttpConnection()){ connection => 
  connection.fetch("http://google.com")
}

//or 

val result = using(new HBaseTable()){ table =>
  table.fetch("myrow", "mycol")
}

Polymorphism

def using[A,B](a: A)(f: A => B): Option[B] = {
  val result = Try(f(a)) 
  //TODO how can we close the resource A?
  result.toOption
}

Polymorphism

def using[A <: java.io.Closeable,B](a: A)(f: A => B): Option[B] = {
  val result = Try(f(a)) 
  a.close()
  result.toOption
}

Polymorphism

val result = using(new HttpConnection()){ connection => 
  connection.fetch("http://google.com")
}

//error: inferred type arguments [HttpConnection,Nothing] do not 
//  conform to method using's type 
//  parameter bounds [A <: java.io.Closeable,B]

Polymorphism

Typeclasses

trait CanClose[A]{
  def close(a: A): Unit 
}

def using[A,B](a: A)(f: A => B)(canClose: CanClose[A]): Option[B] = {
  val result = Try(f(a))
  canClose.close(a) 
  result.toOption
}

Typeclasses

val closeableCanClose = 
  new CanClose[java.io.Closeable] {
    def close(c: java.io.Closeable): Unit = {
      c.close()
    }
  }

val result = using(new HBaseTable()){ table =>
  table.fetch("myrow", "mycol")
}(closeableCanClose)

Typeclasses

def using[A,B](a: A)(f: A => B)(implicit canClose: CanClose[A]): Option[B] = {
  val result = Try(f(a))
  canClose.close(a) 
  result.toOption
}

implicit val closeableCanClose = 
  new CanClose[java.io.Closeable] {
    def close(c: java.io.Closeable): Unit = {
      c.close()
    }
  }

val result = using(new HBaseTable()){ table =>
  table.fetch("myrow", "mycol")
}

Typeclasses

def using[A,B](a: A)(f: A => B)(implicit canClose: CanClose[A]): Option[B] = {
  val result = Try(f(a))
  canClose.close(a) 
  result.toOption
}


//Exactly the same as 

def using[A: CanClose,B](a: A)(f: A => B): Option[B] = {
  val result = Try(f(a))
  implicitly[CanClose[A]].close(a) 
  result.toOption
}

Scalaz to the Rescue!

  • Data Types
    • IO
    • Task
    • \/ (Disjunction)
    • NonEmptyList
    • etc.
  • Better abstractions (type classes)
    • Functor
    • Applicative
    • Monad
    • Foldable
    • Traverse
    • etc.

What is Scalaz?

What is Scalaz?

trait MonadTrans[F[_[_], _]] {
  ...
}

What is Scalaz?

type ->[A, B] = (({type λ[α]=A})#λ) ~> (({type λ[α]=B})#λ)

Scalaz Package Structure

//Data types are under scalaz
import scalaz.NonEmptyList
import scalaz.Kleisli
import scalaz.Zipper
import scalaz.Monad
import scalaz.Functor
//etc

//Or get them all
import scalaz._

Data Types

Scalaz Package Structure

// Adds .right and .left to everything.
import scalaz.syntax.either._

// Adds =/= and === to things that have an Equal typeclass. 
import scalaz.syntax.equal._ 

// Adds .map, strengthL, strengthR to things with Functor typeclass.
import scalaz.syntax.functor._ 

//Or get them all!
import scalaz.syntax.all._

Syntax

Scalaz Package Structure

// Adds .intersperse, .filterM, etc.
import scalaz.syntax.std.list._  

// Adds .toRightDisjunction, etc.
import scalaz.syntax.std.option._ 

//Or get them all!
import scalaz.syntax.std.all._

Syntax

Scalaz Package Structure

//Bring in instances for std lib types
import scalaz.std.option._
import scalaz.std.list._
//etc

Typeclass Instances

https://www.gliffy.com/go/publish/4950560

Let's Dig In!

  • Types
    • NonEmptyList
    • \/ (Disjunction)
    • Validation
  • Typeclasses
    • Semigroup​
    • Monoid
    • Functor
    • Foldable
    • Traverse

OneAnd

final case class OneAnd[F[_], A](head: A, tail: F[A])

//More concretely
type NonEmptyList[A] = OneAnd[List,A]

//Scalaz provides an actual type for NEL though!
final class NonEmptyList[+A] (val head: A, val tail: List[A])

Definition:

NonEmptyList


def nel[A](h: A, t: List[A]): NonEmptyList[A] = new NonEmptyList(h, t)

def nels[A](h: A, t: A*): NonEmptyList[A] = nel(h, t.toList)

Constructors:

Operations:

//Roughly the same as List, except...
val head: A

//In case of emergency you can just call 
def list: List[A] = head :: tail

NonEmptyList

//Shamelessly stolen from Atto

//0 to N matches
def many[A](p: => Parser[A]): Parser[List[A]] 

//1 to N matches
def many1[A](p: => Parser[A]): Parser[NonEmptyList[A]] 

Parsers:

NonEmptyList


def validateUsers(l:List[User]): Validation[NonEmptyList[Error], List[User]] = ...

//OR

def validateUsers(l:List[User]): Validation[NonEmptyList[Error], NonEmptyList[User]] = ...

Validation:

NonEmptyList

Use the types! Don't be a jerk!

Disjunction

sealed trait \/[+A,+B]

//Left disjunction (commonly an error)
final case class -\/[+A](a: A) extends (A \/ Nothing)

//Right disjunction (the success value)
final case class \/-[+B](b: B) extends (Nothing \/ B)

Definition:

Disjunction

//Convert to an option throwing away the error
def toOption: Option[B]

/** Convert to a core `scala.Either` at your own peril. */
def toEither: Either[A, B]

/** Return the right value of this disjunction or the given default if left. Alias for `|` */
def getOrElse[BB >: B](x: => BB): BB 

/** Map on the right of this disjunction. */
def map[D](g: B => D): (A \/ D) 

/** Bind through the right of this disjunction. */
def flatMap[AA >: A, D](g: B => (AA \/ D)): (AA \/ D)

Operations:

Disjunction

type Error = Exception //Could be *anything*

def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...

def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = ??? 

Example:

Disjunction

type Error = Exception //Could be *anything*

def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...

def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = {
  val employeeOrFail = lookupEmployee(name)
  if(employeeOrFail.isRight) {
    val employee = employeeOrFail.toOption.get  
    lookupStartTime(employee.id).map(time => employee -> Date(time))
  } else {
    //log error
  }
}

// Oh god that's ugly (and might not even work)

Example:

Disjunction

type Error = Exception //Could be *anything*

def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...

def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = {
  lookupEmployee(name).flatMap { employee =>
    lookupStartTime(employee.id).map { time =>
      employee -> Date(time)
    }
  }
}

Example:

sealed trait \/[+A,+B]{
  def flatMap[AA >: A, D](g: B => (AA \/ D)): (AA \/ D)
}

Disjunction

type Error = Exception //Could be *anything*

def lookupEmployee(name:String): Error \/ Employee = ...
def lookupStartTime(id: Long): Error \/ Long = ...

def getEmployeeAndStart(name:String): Error \/ (Employee, DateTime) = 
  for {
    employee <- lookupEmployee(name)
    time <- lookupStartTime(employee.id)   
  } yield employee -> Date(time)

Example:

Validation

sealed abstract class Validation[+E, +A]

final case class Success[A](a: A) extends Validation[Nothing, A]
final case class Failure[E](e: E) extends Validation[E, Nothing]

Definition:

Validation

/** Wraps failures in a NonEmptyList */
def toValidationNel: ValidationNel[E, A]

/** Convert to a disjunction. */
def disjunction: (E \/ A) 

/** Throw away the error and return Nil or return the Success in a List */
def toList: List[A]

/** Catamorphism. Run the first given function if failure, otherwise, the second given function. */
def fold[X](fail: E => X, succ: A => X): X

/** Map on the success of this Validation */
def map[B](f: A => B): Validation[E, B]

//Many many more!

Operations:

Validation

  • No .flatMap operation
  • Accumulates errors

Notes

Validation

//The type in [] is the *error* type on success.
(1.success[String] |@| 2.success[String]) {_ + _}
// Success(3)

(1.success[String] |@| "error".failure[Int]) {_ + _}
// Failure(error)

("error2".failure[Int] |@| "error".failure[Int]) {_ * _}
// Failure(error2error)

("error2".failureNel[Int] |@| "error".failureNel[Int]) {_ * _}
// Failure(NonEmptyList(error2,error))

Examples:

Semigroup

 


trait Semigroup[F]  {
  // |+| is also used for this.
  def append(f1: F, f2: => F): F
}

Definition:

Laws:

// Associativity
(a |+| b) |+| c == a |+| (b |+| c)

// Or...
append(append(a, b), c) == append(a, append(b,c))

Semigroup

 


def updateAppendMap[A, B: Semigroup](m: Map[A,B])(k: A, v: B) = {
  val newValue = m.get(k).map(_ |+| v).getOrElse(v)
  m + (k -> newValue)
}

updateAppendMap(Map("A" -> List(1)))("A", List(2))
// Yields Map(A -> List(1, 2))

updateAppendMap(Map("A" -> List(1)))("B", List(2))
// Yields Map(A -> List(1), B -> List(2))

Example:

Monoid


trait Monoid[F] extend Semigroup[F] {
  def zero: F
}

Definition:

Laws:

// Associativity
(a |+| b) |+| c == a |+| (b |+| c)

append(append(a, b), c) == append(a, append(b,c))

// Left identity
a == append(zero, a)

// Right identity
a == append(a, zero)

Monoid

Gives us:

def multiply(value: F, n: Int): F =
  Stream.fill(n)(value).foldLeft(zero)((a,b) => append(a,b))

/** Whether `a` == `zero`. */
def isMZero(a: F)(implicit eq: Equal[F]): Boolean = eq.equal(a, zero)

final def ifEmpty[B](a: F)(t: => B)(f: => B)(implicit eq: Equal[F]): B =
  if (isMZero(a)) { t } else { f }

Monoid

Instances:

  • Int (Product)
  • Int (Sum)
  • List
  • String
  • Option (First)
  • Option (Last)
  • Tuples
  • Map

Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|

def betterSum[A: Monoid](l: TraversableOnce[A]): A = l.foldLeft(Monoid[Option[Int]].zero)((acc, a) => acc |+| a)

betterSum(List("a","b","c"))
// Yields "abc"

betterSum(List(Some(1), Some(2), None))
// Yields Some(3)

Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|
import scalaz.std.map._ // For Map instances

Map("A" -> 2) |+| Map("A" -> 3) 
// Yields Map("A" -> 5)

Map("A" -> 2) |+| Map("B" -> 3) 
// Yields Map("A" -> 2, "B" -> 3)



Monoid

Example :

import scalaz.Monoid
import scalaz.syntax.monoid._ // for |+|
import scalaz.std.map._ // For Map instances

Map("A" -> 2) |+| Map("A" -> 3) 
// Yields Map("A" -> 5)

Map("A" -> 2) |+| Map("B" -> 3) 
// Yields Map("A" -> 2, "B" -> 3)



Map is a Monoid if the values can be appended!

(if there is Semigroup for them)

Monoid

Functor


trait Functor[F[_]] {
  /** Lift `f` into `F` and apply to `F[A]`. */
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

Definition:

Laws:

map(fa)(x => x) == fa

/**
 * A series of maps may be freely rewritten as a single map on a
 * composed function.
 */
map(map(fa)(f1))(f2) == map(fa)(f2 compose f1))

Functor

List(1,2,3).map(_ + 1) //yields List(2,3,4)

Option("hello").map(_.reverse) //yields Some("olleh")


val compositeFunctor = Functor[List].compose(Functor[Option])
compositeFunctor.map(List(Option(1),None, Option(2))){_ + 1} 
  //yields List(Some(2), None, Some(3))

Examples:

Foldable


trait Foldable[F[_]]  {
  /** Map each element of the structure to a [[scalaz.Monoid]], and combine the results. */
  def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B

  /**Right-associative fold of a structure. */
  def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B): B
}

Definition:

Laws:

//foldLeft where you append the value is equal to foldMap
foldMap(fa)(Vector(_)) == foldLeft(fa, Vector.empty[A])(_ :+ _)

//foldRight where you prepend the value is equal to foldMap 
foldMap(fa)(Vector(_)) == foldRight(fa, Vector.empty[A])(_ +: _)

Foldable

//Count how long the foldable structure is
def length[A](fa: F[A]): Int

//Convert to collections
def toList[A](fa: F[A]): List[A]
def toIndexedSeq[A](fa: F[A]): IndexedSeq[A]
def toSet[A](fa: F[A]): Set[A]

//Get Max, Min, etc
def minimumBy[A, B: Order](fa: F[A])(f: A => B): Option[A]
def maximumBy[A, B: Order](fa: F[A])(f: A => B): Option[A]

//foldMap when the value is a Monad.
def foldMapM[G[_], A, B](fa: F[A])(f: A => G[B])(implicit B: Monoid[B], G: Monad[G]): G[B]

//Plus *many* more

Gives Us:

Foldable

List(1,2,3,4).foldMap(identity)
//yields 10

//This is the same as 
List(1,2,3,4).foldLeft(Monoid[Int].zero){(acc,i) => acc |+| i}

Example:

Foldable

List(Map("A" -> List(1,2,3)), Map("A" -> List(4,5,6)), Map("B" -> List(1))).foldMap(identity)

// Yields List(Map("A" -> List(1,2,3,4,5,6), Map("B" -> List(1)))

Example:

Foldable

val listOfMaps = List(Map("A" -> 1), Map("A" -> 2), Map("B" -> 3))

type MapWithStringKey[A] = Map[String,A]
//Compose a nested Foldable
Foldable[List].compose[MapWithStringKey].foldMap(listOfMaps)(_ + 100)
//Result
306

Example:

Traverse


trait Traverse[F[_]] extends Functor[F] with Foldable[F] {
  /** Transform `fa` using `f`, collecting all the `G`s with `ap`. */
  def traverseImpl[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
}

Definition:

Laws:

/**
 *
 * See Traverse.scala, it's verbose...
 *
 */

Traverse

/** A version of `traverse` that infers the type constructor `G`. */
def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit G: Unapply[Applicative, GB]): G.M[F[G.A]]

def sequenceU[A](self: F[A])(implicit G: Unapply[Applicative, A]): G.M[F[G.A]]

/**The composition of Traverses `F` and `G`, `[x]F[G[x]]`, is a Traverse */
def compose[G[_]](implicit G0: Traverse[G]): Traverse[({type λ[α] = F[G[α]]})#λ]

//And more!

Gives Us:

Traverse

Traverse

Examples:

List(1,2,3).map(i => Option(i + 100)).sequenceU
// yields Some(List(101,102,103))

List(1,2,3).map(i => if(i == 1) None else Some(i)).sequenceU
// yields None



Traverse

Examples:

List(1,2,3).map(i => Option(i + 100)).sequenceU
// yields Some(List(101,102,103))

List(1,2,3).map(i => if(i == 1) None else Some(i)).sequenceU
// yields None



.map + sequenceU == traverseU

Traverse

List(1,2,3).traverseU(i => Option(i + 100))
// yields Some(List(101,102,103))

List(1,2,3).traverseU(i => if(i == 1) None else Some(i))
// yields None



Examples:

Traverse

type MyValidation[A] = Validation[NonEmptyList[String], A]

def validateUser(u: String): MyValidaton[User] = ...

List("coltfred", "bob", "%%%%%%%", "$$$$$").traverseU(validateUser(_))

// yields Failure(NonEmptyList("'%%%%%%%' isn't a valid user","'$$$$$' isn't a valid user"))



Examples:

Traverse

type MyValidation[A] = Validation[NonEmptyList[String], A]

def validateUser(u: String): MyValidaton[User] = ...

List("coltfred", "bob").traverseU(validateUser(_))

// yields Success(List(User("coltfred"), User("bob")))



Examples:

Thank you!

Questions?

Ask now or come to #scalaz IRC!

Scala - Just Do It!

By Colt Frederickson

Scala - Just Do It!

Big Sky Dev Con 2015 talk.

  • 1,887