Practical Scalaz

Gregg Hernandez <gregg@lucidchart.com>

golucid.co

golucid.co

golucid.co

Scalaz

An extension to the core Scala library for functional programming.

imports

import scalaz._
import Scalaz._

imports

import scalaz.std.option._
import scalaz.std.list._
import scalaz.std.int._
// etc.

Boolean

import scalaz.syntax.std.boolean._

Boolean

def runQuery(count: Option[Int]) = ???

val limit = 10
val limitQuery = true

val count = 
  if (limitQuery) Some(limit) else None

runQuery(count)

Boolean

def runQuery(count: Option[Int]) = ???

val limit = 10
val limitQuery = true

runQuery(limitQuery.option(limit))

Boolean

def getOne() = ???
def getAll() = ???

val all = true

if (all) {
  getAll()
} else {
  getOne()
}

Boolean

def getOne() = ???
def getAll() = ???

val all = true

all.fold(getAll(), getOne())

Boolean

def close(): Unit = ???

val doorIsOpen = true

when(doorIsOpen)(close()) // closes door

Boolean

def close(): Unit = ???

val doorIsOpen = true

unless(doorIsOpen)(close()) // does nothing

Equal

import scalaz.syntax.equal._

Equal

1 == "1"

Equal

1 == "1"

[warn] comparing values of types Int and String 
[warn] using `==' will always yield false
[warn]     1 == "1"
[warn]       ^

Equal

"1" == 1

Equal

"1" == 1 // compiles with no errors or warnings
         // always false

if ("1" == 1) {
  println("this will never print")
}

Equal

boolean	equals(Object obj)

def ==(arg: Any): Boolean

Equal

class EqualOps[F] {
  def ===(other: F): Boolean
}

Equal

1 === "1"
    
"1" === 1

Equal

1 === "1"
[error]  found   : String("1")
[error]  required: Int
[error]     1 === "1"
[error]           ^

"1" === 1
[error]  found   : Int(1)
[error]  required: String
[error]     "1" === 1
[error]             ^

Equal

class Foo(val a: String, val b: Int)
object Foo {
  implicit eq = Equal.equal[Foo] { (x, y) =>
    x.a === y.a && x.b === y.b
  }
}

new Foo("hi", 1) === new Foo("hi", 1) // true

Equal

case class Foo(a: String, b: Int)
object Foo {
  implicit eq = Equal.equalA[Foo]
}

Foo("hi", 1) === Foo("hi", 1) // true

Memo

Memo

def slowFib(n: Int): Int = n match {
  case 0 => 0
  case 1 => 1
  case n => slowFib(n - 2) + slowFib(n - 1)
}

slowFib(30)
slowFib(40)
slowFib(45)

[success] Total time: 7 s

Memo

val cache = mutable.Map.empty[Int, Int]

def slowFib(n: Int): Int = {
  if (!cache.contains(n)) {
    cache(n) = n match {
      case 0 => 0
      case 1 => 1
      case n => slowFib(n - 2) + slowFib(n - 1)
    }
  }

  cache(n)
}

Memo

lazy val memoizedFib: Int => Int = {
  Memo.mutableHashMapMemo[Int, Int] { n =>
    n match {
      case 0 => 0
      case 1 => 1
      case n => 
        memoizedFib(n - 2) + memoizedFib(n - 1)
    }
  }
}

Memo

lazy val memoizedFib: Int => Int = ???

memoizedFib(30)
memoizedFib(40)
memoizedFib(45)

[success] Total time: 0 s

Memo

Memo.arrayMemo
Memo.doubleArrayMemo
Memo.immutableHashMapMemo
Memo.immutableListMapMemo
Memo.immutableMapMemo
Memo.immutableTreeMapMemo
Memo.mutableHashMapMemo
Memo.mutableMapMemo
Memo.nilMemo
Memo.weakHashMapMemo

Option

import scalaz.std.option._
import scalaz.syntax.std.option._

Option

def runQuery(count: Option[Int]) = ???

runQuery(Some(10))

Option

def runQuery(count: Option[Int]) = ???

runQuery(10.some)

Option

val l = List(1,2,3,4,5)

// verify that no items are > 2
l.foldLeft(None) { case (sofar, next) =>
  sofar.orElse((next > 2).option(s"$next > 2"))
}

Option

val l = List(1,2,3,4,5)

// verify that no items are > 2
l.foldLeft(None) { case (sofar, next) =>
  sofar.orElse((next > 2).option(s"$next > 2"))
}

[error] type mismatch;
[error]  found   : Option[String]
[error]  required: None.type
[error]       sofar.orElse((next > 2).option(s"$next > 2"))
[error]                   ^

Option

// Scala
object None extends Option[Nothing]

Option

val l = List(1,2,3,4,5)

// verify that no items are > 2
l.foldLeft(none[String]) { case (sofar, next) =>
  sofar.orElse((next > 2).option(s"$next > 2"))
}

Option

// Scala
object None extends Option[Nothing]

// Scalaz
def none[A]: Option[A] = None

Not just Scalaz

val l = List(1,2,3,4,5)

// verify that no items are > 2
l.foldLeft(Option.empty[String]) { 
  case (sofar, next) =>
    sofar.orElse((next > 2).option(s"$next > 2"))
  }

Not just Scalaz

// Scala
object None extends Option[Nothing]

// Scalaz
def none[A]: Option[A] = None

// Scala
def empty[A]: Option[A] = None

Either

import scalaz.std.either._
import scalaz.syntax.std.either._

Either

def handleString(s: String) = ???
def handleInt(i: Int) = ???

val r: Either[String, Int] = Right(10)
val l: Either[String, Int] = Left("hello")

// calls handleInt(10)
r.fold(handleString(_), handleInt(_))

// calls handleString("hello")
l.fold(handleString(_), handleInt(_))

Either

def handleString(s: String) = ???
def handleInt(i: Int) = ???

val r: Either[String, Int] = Right(10)

r match {
  case Right(i) => handleInt(i)
  case Left(s) => handleString(s)
}

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Either[String, Int] = Right(100)

r.map(subtract).map(divide)

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Either[String, Int] = Right(100)

r.map(subtract).map(divide)

[error] value map is not a member of Either
[error]     r.map(subtract).map(divide)
[error]       ^

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Either[String, Int] = Right(100)

r.right.map(subtract).right.map(divide)
// Right(11)

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: \/[String, Int] = 100.right

r.map(subtract).map(divide) // 11.right

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: String \/ Int = 100.right

r.map(subtract).map(divide) // 11.right

Either

type Xor[A, B] = A \/ B

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Xor[String, Int] = 100.right

r.map(subtract).map(divide) // 11.right

Either

type Xor[A, B] = A \/ B

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Xor[String, Int] = 10.right

r.map(subtract).map(divide)

Either

type Xor[A, B] = A \/ B

def subtract(i: Int): Int = i - 10
def divide(i: Int): Int = 1000 / i

val r: Xor[String, Int] = 10.right

r.map(subtract).map(divide)

[error] java.lang.ArithmeticException: / by zero

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Xor[String, Int] = 
  if (i == 0) {
    "Divide by zero".left
  } else {
    (1000 / i).right
  }

val r: Xor[String, Int] = 10.right

r.map(subtract).flatMap(divide)
// "Divide by zero".left

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Xor[String, Int] = 
  if (i == 0) {
    "Divide by zero".left
  } else {
    (1000 / i).right
  }

val r: Xor[String, Int] = 100.right

r.map(subtract).flatMap(divide)
// 11.right

Either

def subtract(i: Int): Int = i - 10
def divide(i: Int): Xor[String, Int] = ???

val r: Xor[String, Int] = 100.right

for {
  x <- r.map(subtract)
  y <- divide(x)
} yield y

// 11.right

OneAnd

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

OneAnd

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

type NonEmptyList[A] = OneAnd[List, A]

OneAnd

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

type NonEmptyList[A] = OneAnd[List, A]
type NonEmptySet[A] = OneAnd[Set, A]

OneAnd


def getUsers(ids: List[Int]): List[Users] = {
  sql"SELECT * FROM users WHERE id IN ($ids)"
    .as[List[User]]
}

getUsers(Nil)

OneAnd


def getUsers(ids: List[Int]): List[Users] = {
  sql"SELECT * FROM users WHERE id IN ($ids)"
    .as[List[User]]
}

getUsers(Nil) 

// runtime error: invalid sql syntax
// SELECT * FROM users WHERE id IN ()

OneAnd


def getUsers(ids: NonEmptyList[Int]): List[Users] = {
  sql"SELECT * FROM users WHERE id IN ($ids)"
    .as[List[User]]
}

getUsers(Nil) // Won't compile

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]

Validation

def gtTen(i: Int): Validation[String, Int]

def containsOne(i: Int): Validation[String, Int]

val i = 11
val j = 12
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Success(132)

Validation

def gtTen(i: Int): Validation[String, Int]

def containsOne(i: Int): Validation[String, Int]

val i = 9
val j = 12
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Failure(9 <= 10)

Validation

def gtTen(i: Int): Validation[String, Int]

def containsOne(i: Int): Validation[String, Int]

val i = 11
val j = 22
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Failure(22 does not contain 1)

Validation

def gtTen(i: Int): Validation[String, Int]

def containsOne(i: Int): Validation[String, Int]

val i = 9
val j = 22
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Failure(9 <= 1022 does not contain 1)

Validation

def gtTen(i: Int): ValidationNel[String, Int]

def containsOne(i: Int): ValidationNel[String, Int]

Validation

Validation[NonEmptyList[String], Int]

ValidationNel[String, Int]

Validation

type ValidationNel[E, +X] = 
     Validation[NonEmptyList[E], X]

Validation

def gtTen(i: Int): ValidationNel[String, Int]

def containsOne(i: Int): ValidationNel[String, Int]

(gtTen(11) |@| containsOne(12)) { _ * _ }
//Success(132)
(gtTen(9) |@| containsOne(12)) { _ * _ }
//Failure(NonEmpty[9 <= 10])
(gtTen(11) |@| containsOne(22)) { _ * _ }
//Failure(NonEmpty[22 does not contain 1]
(gtTen(9) |@| containsOne(22)) { _ * _ }
//Failure(NonEmpty[9 <= 10,22 does not contain 1])

Validation

def email(s: String) = ???
def phone(s: String) = ???
def addr(s: String) = ???

(email(e) |@| phone(p) |@| addr(a)) { (e, p, a) =>
  UserInfo(e, p, a)
}

Monad Transformers

def getUser(id: Int): ?

Monad Transformers

def getUser(id: Int): User

Monad Transformers

def getUser(id: Int): Option[User]

Monad Transformers

def getUser(id: Int): Future[Option[User]]

Monad Transformers

def getUser(id: Int): Future[Option[User]]

getUser(10).map { user: Option[User] =>

}

Monad Transformers

def getUser(id: Int): Future[Option[User]]

getUser(10).map { user: Option[User] =>
  user.map { user: User =>
    user.email
  }
}

Monad Transformers

def getUser(id: Int): Future[Option[User]]

val email: Future[Option[String]] = 
  getUser(10).map { user: Option[User] =>
    user.map { user: User =>
      user.email
    }
  }

Monad Transformers

def getUser(id: Int): Future[Option[User]]

getUser(10).map { user: User =>
  user.email
}

Transformers

case class OptionT[F[_], A](run: F[Option[A]])

Transformers

def getUser(id: Int): Future[Option[User]]

OptionT[Future, User](getUser(10)).map { user =>
  user.email
}

Transformers

def getUser(id: Int): OptionT[Future, User]

getUser(10).map { user: User =>
  user.email
}

Transformers

def getUser(uid: Int): OptionT[Future, User]
def getTeam(u: User): OptionT[Future, Team]
def getSub(t: Team): OptionT[Future, Subscription]

getUser(userId).flatMap { user =>
  getTeam(user).flatMap { team =>
    getSub(team).map { sub =>
      sub.renewalDate
    }
  }
}

Transformers

def getUser(uid: Int): OptionT[Future, User]
def getTeam(u: User): OptionT[Future, Team]
def getSub(t: Team): OptionT[Future, Subscription]

for {
  user <- getUser(userId)
  team <- getTeam(user)
  sub <- getSub(team)
} yield {
  sub.renewalDate
}

Transformers

def getUser(uid: Int): Future[Option[User]]
def getTeam(u: User): Future[Option[Team]]
def getSub(t: Team): Future[Option[Subscription]]

val rdate: ? = getUser(uid).map { userOpt =>
  userOpt.map { user =>
    getTeam(user).map { teamOpt =>
      teamOpt.map { team =>
        getSub(team).map { subOpt =>
          subOpt.map { sub =>
            sub.renewalDate
          }}}}}}

Transformers

def getUser(uid: Int): Future[Option[User]]
def getTeam(u: User): Future[Option[Team]]
def getSub(t: Team): Future[Option[Subscription]]

type Ouch =
  Future[Option[Future[Option[Future[Option[Date]]]]]]
val rdate: Ouch = getUser(uid).map { userOpt =>
  userOpt.map { user =>
    getTeam(user).map { teamOpt =>
      teamOpt.map { team =>
        getSub(team).map { subOpt =>
          subOpt.map { sub => sub.renewalDate
        }}}}}}

Transformers

def getUser(uid: Int): OptionT[Future, User]
def getTeam(u: User): OptionT[Future, Team]
def getSub(t: Team): OptionT[Future, Subscription]

val rdate: OptionT[Future, Date] = for {
  user <- getUser(userId)
  team <- getTeam(user)
  sub <- getSub(team)
} yield {
  sub.renewalDate
}

rdate.run: Future[Option[Date]]

Transformers

OptionT[F[_], A]  <=> F[Option[A]]

EitherT[F[_], A, B] <=> F[Either[A, B]]

ListT[F[_], A] <=> F[List[A]]

Transformers

OptionT[Future, A] <=> Future[Option[A]]

EitherT[Future, A, B] <=> Future[Either[A, B]]

ListT[Future, A] <=> Future[List[A]]

Applicative

def gtTen(i: Int): Validation[String, Int]

def containsOne(i: Int): Validation[String, Int]

val i = 11
val j = 12
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Success(132)

Applicative

val a = Option(99)
val b = Option("hello")
val c = Option(1.2)

(a |@| b |@| c) { (a, b, c) =>
  s"$a $b $c"
}

// Some(99 hello 1.2)

Applicative

val a = Option(99)
val b = Option.empty[Int]
val c = Option(1.2)

(a |@| b |@| c) { (a, b, c) =>
  s"$a $b $c"
}

// None

Applicative

val a = List(1,2)
val b = List(3,4)

(a |@| b) { (a, b) =>
  (a, b)
}

Applicative

val a = List(1,2)
val b = List(3,4)

(a |@| b) { (a, b) =>
  (a, b)
}

// List((1,3), (1,4), (2,3), (2,4)

Applicative

val a = List(1,2)
val b = List(3,4)

(a |@| b) { (a, b) =>
  a * b
}

Applicative

val a = List(1,2)
val b = List(3,4)

(a |@| b) { (a, b) =>
  a * b
}

// 1 * 3
// 1 * 4
// 2 * 3
// 2 * 4
// List(3, 4, 6, 8)

Applicative

val a = Future(1)
val b = Future(2)
val c = Future(3)

(a |@| b |@| c) { (a, b, c) =>
  a * b * c
}

// Future(6)

Applicative

val a = Future(1)
val b = Future[Int](throw new Exception("bad"))
val c = Future(3)

val res = (a |@| b |@| c) { (a, b, c) =>
  a * b * c
}

println(res)
// Failure(java.lang.Exception: bad)

Semigroup

1 |+| 1

Semigroup

1 |+| 1

// 2

Semigroup

List(1,2) |+| List(3,4)

Semigroup

List(1,2) |+| List(3,4)

// List(1,2,3,4)

Semigroup

Map("a" -> 1) |+| Map("b" -> 2)

Semigroup

Map("a" -> 1) |+| Map("b" -> 2)

// Map("a" -> 1, "b" -> 2)

Semigroup

Map("a" -> 1) |+| Map("a" -> 1)

Semigroup

Map("a" -> 1) |+| Map("a" -> 1)

// Map("a" -> 2)

Semigroup

val a = Map("a" -> 
          Map("b" -> 
            Map("c" -> List(1,2,3))))
val b = Map("a" -> 
          Map("b" -> 
            Map("c" -> List(4,5,6))))

a |+| b 

// Map(a -> 
//   Map(b -> 
//     Map(c -> List(1, 2, 3, 4, 5, 6))))

References

Cats typelevel.org/cats

Learning Scalaz eed3si9n.com/learning-scalaz/

Thanks

@gregghz

gregg@lucidchart.com

github.com/gregghz

golucid.co

Practical Scalaz

By Gregg H

Practical Scalaz

  • 2,096