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)
But they are the same
Everything written with cats
can be written with scalaz
libraryDependencies += "org.typelevel" %% "cats" % "0.9.0"
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(",")})
"""
}
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
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
}
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
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)
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])
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
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
}
trait
implemented on the flymap
method
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)
ap
method def ap(ff: F[A => B])(f:F[A]):F[B]
pure
Traverse.sequence (F[G[A]] => G[F[A]]
) works only if
F is a Functor and
G is an Applicative
flatMap
methodpure
MonadTransformer only works with 2 Monads
Theoritically, for comprehension works correctly only with Monad
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)
)
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)