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