Scalaz

Cats

http://typelevel.org/cats/

Scalaz

Cats

http://typelevel.org/cats/

What are Scalaz / Cats

Functionnal programming libraries - General Utility for Scala

Why not Scalaz?

The community

The lack of documentation... on purpose


  def contra[T[-_], A, A2](a: A <~< A2): (T[A2] <~< T[A]) =
      a.subst[λ[`-α` => T[A2] <~< T[α]]](refl)
  	  
/**We can lift subtyping into any contravariant type constructor */

Symbols everywhere


(1 |-> 5) >>= (_ |--> (2,10))
// is the same as
(1 to 5).flatMap(x => x to 10 by 2)
            

So why Cats ?

  • Community (TypeLevel project)
  • Documentation

But they are the same

Everything written with cats
can be written with scalaz

How to start?

What can we do with Cats

NonEmptyList


def first(numbers: List[Int]): Int = numbers.head

first(List(1,2,3)) // 1
first(List.empty)
// java.util.NoSuchElementException: head of empty list
          

def first(nel: NonEmptyList[Int]): Int = nel.head

first(NonEmptyList.of(1,2,3)) // 1
          


// An empty List in SQL "IN" trigger an error
def findEmail(ids: NonEmptyList[Int]) = {
  """SELECT email
     FROM users
     WHERE id in (${ids.toList.mkString(",")})
  """
}
          

Traverse.sequence

F[G[A]] -> G[F[A]]

import cats.instances.all._
import cats.syntax.traverse._
import scala.concurrent.ExecutionContext.Implicits.global

val maybeEventualEmail: Option[Future[String]] =
      Some(Future.successful("admin@mnubo.com"))

val eventualMaybeEmail: Future[Option[String]] =
      maybeEventualEmail.sequence

val listMaybeInt: List[Either[String, Int]] =
      List(Right(1), Right(3))

//sequenceU because Either has 2 parameters
val maybeListInt: Either[String, List[Int]] =
      listMaybeErrors.sequenceU
          

Traverse.flatSequence


import cats.instances.all._
import cats.syntax.traverse._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

def findCurrentUserId(): Option[Long] = ???

def findNameByUserId(id: Long): Future[Option[String]] = ???

def findCurrentName(): Future[Option[String]] = {
  val eventualName: Option[Future[Option[String]]] =
    findCurrentUserId().map(findNameByUserId)

  eventualName.flatSequence
}

          

Validation - Option


case class User(id:Int, name:String)

def isPositive(i: Int): Option[Int] = if(i > 0) Some(i) else None
def nonEmpty(s: String): Option[String] =
  if (!s.isEmpty) Some(s) else None

def validate(id:Int, name:String):Option[User] = for {
 okId   <- isPositive(id)
 okName <- nonEmpty(name)
} yield User(okId, okName)

validate( 1, "Georges") // Some(User(1, "Georges"))
validate( 1, "")        // None
validate(-1, "Georges") // None
validate(-1, "")        // None
          

Validation - Either 2.12


def validate(id:Int, name:String): Either[String, User] = for {
  okId   <- isPositive(id) toRight s"id $id must be > 0"
  okName <- nonEmpty(name) toRight "name must be non empty"
} yield User(okId, okName)

validate( 1, "Georges") // Right(User(1,Georges))
validate( 1, "")        // Left(name must be non empty)
validate(-1, "Georges") // Left(id -1 must be > 0)
validate(-1, "")        // Left(id -1 must be > 0)
  

Validation - Validated


import cats.syntax.option._
import cats.syntax.cartesian._

def validate(id:Int, name:String):ValidatedNel[String, User] = {
  ( isPositive(id).toValidNel(s"id $id must be > 0")    |@|
    nonEmpty(name).toValidNel("name must be non empty")
    ).map(User.apply)
}

validate(1, "Georges")  // Valid(User(1,Georges))
validate(1, "")         // Invalid(NonEmpty[name must be non empty])
validate(-1, "Georges") // Invalid(NonEmpty[id -1 must be > 0])
validate(-1, "")
// Invalid(NonEmpty[id -1 must be > 0,name must be non empty])
          

Big Bad Words incoming

MonadTransformer


def findName(): Future[Option[String]] = ???

val result: Future[Option[Long]] =
  findName().map(_.map(_.length))



def findName(): Future[Option[String]] = ???
val result: Future[Option[Long]] =
  OptionT(findName()).map(_.length).value

MonadTransformer


def findCurrentUserId(): Future[Option[Long]] = ???

def findNameByUserId(id: Long): Future[Option[String]] = ???

def findCurrentName(): Future[Option[String]] = {
 val eventualMaybeName: OptionT[Future, String] = for {
     userId <- OptionT(findCurrentUserId())
     name <- OptionT(findNameByUserId(userId))
   } yield name
 )

 eventualMaybeName.value
}

Some more abstract things

(If we have time)

Typeclasses

  • A way to dynamically add methods to existing object
  • Can be seen as some kind of trait implemented on the fly

Functor

  • A class with one type parameter
  • and the map method
  • List, Option, Future, Try, etc.

Example


def add1(o: Option[Int]): Option[Int] = o.map(_ + 1)
def add1(o: Future[Int]): Future[Int] = o.map(_ + 1)
          

import cats.Functor
import cats.implicits._

def add1[M[_]: Functor](o: M[Int]):M[Int] = o.map(_ + 1)

add1(Option(2)) // Some(3)
add1(Try(5)) // Success(6)
          

Applicative

  • A class with one type parameter
  • an ap method
    def ap(ff: F[A => B])(f:F[A]):F[B]
  • and a constructor named pure
  • List, Option, Future, Try, etc.

Traverse.sequence (F[G[A]] => G[F[A]]) works only if
F is a Functor and
G is an Applicative

Monad

  • A class with one type parameter
  • the flatMap method
  • and a constructor named pure
  • List, Option, Future, Try, etc.

MonadTransformer only works with 2 Monads

Theoritically, for comprehension works correctly only with Monad

An Example


sealed trait ApiResponse[+A]
case class ApiSuccess[A](content: A) extends ApiResponse[A]
case class ApiError(status: Int) extends ApiResponse[Nothing]

val l: List[ApiResponse[Int]] = List(
  ApiSuccess(1),
  ApiSuccess(10),
  ApiSuccess(100)
)
          

An Example


implicit val  applicativeApiResponse: Applicative[ApiResponse] =
  new Applicative[ApiResponse] {
    override def pure[A](a: A): ApiResponse[A] = ApiSuccess(a)

    override def ap[A, B](ff: ApiResponse[(A) => B])(fa: ApiResponse[A]):ApiResponse[B] =
      fa match {
        case ApiSuccess(content) => ff match {
          case ApiSuccess(f) => ApiSuccess(f(content))
          case err@ApiError(_) => err
        }
        case err@ApiError(_) => err
      }
  }

val s:ApiResponse[List[Int]] = l.sequence

l.head.map(_ + 1)
          

Other things

  • other Type classes (Monoid, Foldable, Traverse, Apply, etc.)
  • Free
  • State
  • etc.

Typelevel

http://typelevel.org

  • Shapeless (type level programming)
  • Doobie (SQL)
  • Monocle (Lenses)
  • Scalacheck (Property based testing)
  • Simulacrum (Type classes)
  • etc.

Questions?