Practical Scalaz

Gregg Hernandez <gregg@lucidchart.com>

golucid.co

golucid.co

golucid.co

Scalaz

An extension to the core Scala library for functional programming.

Cats

An extension to the core Scala library for functional programming.

imports

// scalaz
import scalaz._
import Scalaz._

// cats
import cats._
import cats.data._
import cats.implicits._

// mouse (cats helper)
import mouse.all._

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 getFromNetwork(): String = ???
def getFromDisk(): String = ???

val useNetwork = true

if (useNetwork) {
  getFromNetwork()
} else {
  getFromDisk()
}

Boolean

def getFromNetwork(): String = ???
def getFromDisk() = ???

val useNetwork = true

all.fold(getFromNetwork(), getFromDisk())

Equal (Eq in cats)

1 == "1"

Equal (Eq in cats)

1 == "1"

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

Equal (Eq in cats)

"1" == 1

Equal (Eq in cats)

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

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

Equal (Eq in cats)

// java
boolean	equals(Object obj)

// scala
def ==(arg: Any): Boolean

Equal (Eq in cats)

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

Equal (Eq in cats)

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 (Eq in cats)

class Foo(val a: String, val b: Int)
object Foo {
       // cats: Eq.instance[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 (Eq in cats)

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

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

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 / cats
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 / cats
def none[A]: Option[A] = None

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

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

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

val r: Either[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): Either[String, Int] = 
  if (i == 0) {
    "Divide by zero".left
  } else {
    (1000 / i).right
  }

val r: Either[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): Either[String, Int] = 
  if (i == 0) {
    "Divide by zero".left
  } else {
    (1000 / i).right
  }

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

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

Either

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

val r: Either[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

             // cats: Validated[+E, +A]
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)
}

Validated

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

(email(e), phone(p), addr(a)).mapN { (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
}

Monad Transformers

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

Monad Transformers

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

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

Monad Transformers

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

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

Monad 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
    }
  }
}

Monad 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
}

Monad 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
          }}}}}}

Monad 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
        }}}}}}

Monad 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]]

Monad Transformers

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

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

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

Monad Transformers

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

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

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

Monad Transformers

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

Monad Transformers

Nested(Future(List(1,2,3))).map { x =>
  println(x)
}

// 1
// 2
// 3

Applicative

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

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

val i = 11
val j = 12
// cats
// (getTen(i), containsOne(j)).mapN { _ * _ }
(gtTen(i) |@| containsOne(j)) { _ * _ }
// Success(132)

Applicative

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

// (a, b, c).mapN { (a, b, c) =>
(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).mapN { (a, b, c) =>
(a |@| b |@| c) { (a, b, c) =>
  s"$a $b $c"
}

// None

Applicative

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

// (a, b).mapN { (a, b) =>
(a |@| b) { (a, b) =>
  (a, b)
}

Applicative

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

// (a, b).mapN { (a, b) =>
(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).mapN { (a, b) =>
(a |@| b) { (a, b) =>
  a * b
}

Applicative

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

// (a, b).mapN { (a, b) =>
(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).mapN { (a, b, c) =>
(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)

       // (a, b, c).mapN { (a, b, c) =>
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 (Utah Scala Meetup)

By Gregg H

Practical Scalaz (Utah Scala Meetup)

  • 1,245