2. About me:
• Software / Data Engineer at
• Develops AdTech Platform
• Research department
• Works on processing lots of data really fast
• Scala since ~2011
3. Warning!
• This presentation is intended to be practical guide how
to start with FP and cats
• May contain inaccurate information on purpose
• May contain simply wrong information because of my
mistake for which I sincerely apologize - I’m no expert
• Questions during presentation will be highly
appreciated
4. Why this talk? (1)
• FP is a different kind of beast
• Does not reassembles anything you know
• Reading blogs do not help, it makes it probably even
worse
• It’s frustrating
5. Why this talk? (2)
• There is much to learn
• Accepting this is the first step you have to make
• I’ve made some of the hops and I want to share so
you don’t have to go through some of the daunting
things
6. What FP is all about?
(1)
• Functions?
• Higher order functions?
• Immutability?
• Referential transparency?
7. What FP is all about?
(2)
• How stupid it may sound FP is about values
• Values are: bits, ints, data structures,
functions, types
• What FP does for us it allows us to
combine values and look at them as a new
value
• This is what we’ll be doing throughout the
presentation
16. <console>:13: error: class Option takes type parameters
val optionSemigroup = new Semigroup[Option] {}
val optionSemigroup = new Semigroup[Option[Int]] {}
This would do the job but we can do better
17. Higher Kinded Types
• Option, List ect. are type constructors.
Like normal constructors but for types.
• They take a type as argument
• Some say that they have a “hole”
• Scala compiler knows about this
18. scala> :kind -v String
java.lang.String's kind is A
*
This is a proper type.
scala> :kind -v Either
scala.util.Either's kind is F[+A1,+A2]
* -(+)-> * -(+)-> *
This is a type constructor: a 1st-order-kinded type.
scala> :kind -v Option
scala.Option's kind is F[+A]
* -(+)-> *
This is a type constructor: a 1st-order-kinded type
19. Now the scary part
scala> :kind -v trait Foo[X[_]]
Foo's kind is X[F[A]]
(* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kin
at when you put inside a type that when put inside another type create
24. Semigroup[Int]
What is this?
object Semigroup extends SemigroupFunctions[Semigroup] {
@inline
final def apply[A](implicit ev: Semigroup[A]): Semigroup[A] = ev
}
25. Creating Effects
• This is very common in Cats
• TypeConstructor[X].someMethod
• Heavy use of implicits and packages
objects
26. • import cats._
make all types available in scope like
Semigroup, Monad, Applicative ect.
Like Predef for Scala.
• import cats.implicits._
puts ALL implicit into the scope using
package object trick
28. Instances
trait ListInstances extends cats.kernel.instances.ListInstances {
implicit val catsStdInstancesForList:
TraverseFilter[List]
with MonadCombine[List]
with Monad[List]
with CoflatMap[List]
with RecursiveTailRecM[List] =
new TraverseFilter[List]
with MonadCombine[List]
with Monad[List]
with CoflatMap[List]
with RecursiveTailRecM[List] {
def combineK[A](x: List[A], y: List[A]): List[A] = x ++ y
(...)
}
30. Syntax
trait SemigroupSyntax {
implicit def catsSyntaxSemigroup[A: Semigroup](a: A):
SemigroupOps[A] = new SemigroupOps[A](a)
}
final class SemigroupOps[A: Semigroup](lhs: A) {
def |+|(rhs: A): A = macro Ops.binop[A, A]
def combine(rhs: A): A = macro Ops.binop[A, A]
}
List(1) |+| List(2, 2, 4)
We can do both:
List(1) combine List(2, 2, 4)
31. Monoid(K)
trait Monoid with Semigroup[A] {
def empty: A
}
@typeclass
trait MonoidK[F[_]] extends SemigroupK[F] { self =>
def empty[A]: F[A]
}
• @typeclass annotation comes from simulacrum
• It generates code
• “Jump to source” might not work in Cats src code
33. Can we always create a monoid?
type NEL[A] = (A, List[A])
val nelMonoid = new MonoidK[NEL] {
def combineK[A](n1: NEL[A], n2: NEL[A]): NEL[A] =
(n1._1, (n1._2 :+ n2._1) ++ n2._2)
def empty[A]: NEL = ???
}
34. • One may end-up writing effect class for some data
structure that it is impossible
• http://stackoverflow.com/questions/32477292/fold-on-
nonemptylist/32479640#32479640
• It took me about 2 hours to realize this
35. trait Ord
case object GT extends Ord
case object LT extends Ord
case object EQ extends Ord
object Ord {
implicit object OrderingMonoid extends Monoid[Ord] {
def empty(): Ord = EQ
def combine(x:Ord, y: Ord): Ord = x match {
case EQ => y
case LT => LT
case GT => GT
}
}
}
Monoid example
36. def palindromeFirst(s1: String, s2: String): Ord
def shorterFirst(s1: String, s2: String): Ord
val res = List(palindromeFirst _, shorterFirst _).map{ f =>
f("ANNA", “BARBARA”)
}
Foldable[List].fold(res)(implicit OrderingMonoid)
// res0(: Ord = GT
37. Foldable
• Fold is surprisingly powerful
http://www.cs.nott.ac.uk/~pszgmh/fold.pdf
• Foldable has most of the collections api on it:
find, exists, forAll, filter, isEmpty,
• It allows to reduce collection to single element
• It can make use of Monoids
38. Foldable and monoid
example 2
def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B =
foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a)))
val lineItems: List[LineItem] = ...
//explicit summoning Foldable
val totalInvoiceValue = Foldable[List].foldMap(lineItems){_.value}
//using syntax ops
val totalInvoiceValue = lineItems.foldMap { _.value }
58. Monads
• You use them every day map/flatMap
• Monads are powerful abstraction
• They have most of the combinators
• At the same time not all data structures
can be expressed as Monads
60. class DBRepo[F[_]] {
def getUserLoging(id: Long)(implicit F: Monad[F]): F[String] =
F.pure(id.toString)
def getUserEmail(id: Long)(implicit F: Monad[F]): F[String] =
F.pure(id.toString)
def getUser(id: Long)(implicit F: Monad[F]) : F[User] =
for {
login <- getUserLoging(id)
email <- getUserEmail(id)
} yield User(login, email)
}
val repo1 = new DBRepo[Future]
val repo2 = new DBRepo[Task]
61. Monads
for {
i <- List(Option(1), Option(2))
j <- List(Option(3), Option(4))
} yield i + j
Monads don’t compose (usually), so the two below won’t wor
Monad[List] compose Monad[Option]
62. Monads
this will, but it’s ugly
val p = for {
i <- List(Option(1), Option(2))
j <- List(Option(3), Option(4))
} yield {
for {
k <- i
l <- j
} yield k+l
}
63. Monad transformers
val k = for {
i <- OptionT(List[Option[Int]](Option(1), Option(2)))
j <- OptionT(List[Option[Int]](Option(3), Option(4)))
} yield i + j
…this will also and it’s nice:
64. Monad transformers
• Cats have multiple instances of those
• EitherT, IdT, OptionT, StateT, WriterT
• TypeT[F[_], A] wraps F[Type[A]]
• E.g. OptionT[List, Int] wraps List[Option[Int]]
65. Effectful functions
• A => F[B]
• Returned value in some kind of
effect/context
• More common than one might think
66. // Id => Future[Long]
def getCustomerById(long: Id): Future[Customer]
// CharSequence => Option[String]
def findFirstIn(source: CharSequence): Option[String]
//Int => List[Int]
def listFromZeroToN(n: Int): List[Int]
we want to combine those
67. Kleisli
final case class Kleisli[F[_], A, B](run: A => F[B])
• It has all the good’ol combinators: flatMap, map, compose, apply ect.
• Used for composing effectful functions
• What kind of combinator can you use depends on what F is
• If you can have implicit effect for F you can call certain methods
def map[C](f: B => C)
(implicit F: Functor[F]): Kleisli[F, A, C] =
Kleisli(a => F.map(run(a))(f))
def flatMap[C](f: B => Kleisli[F, A, C])
(implicit F: FlatMap[F]): Kleisli[F, A, C] =
Kleisli((r: A) => F.flatMap[B, C](run(r))((b: B) => f(b).run(r)))
68. Kleisli
(A => B) andThen (B => C) => (A => C)
(A => F[B]) andThen (B => F[C]) => won't work
Kleisli(A => F[B]) andThen Kleisli(B => F[C]) => Kleisli(A => F[C])
69. There is more…
• Xor
• State
• Validated
• FreeMonads and FreeApplicatives
• Show
• Traverse
70. Simple RPC
• Let’s build a quick RPC API with
focus on HTTP
• We’ll take building blocks from what
we’ve seen
72. package object http {
type Service[A,B] = Kleisli[Future, A, B]
type HttpService = Service[Request, Response]
}
73. package object http {
type Service[A,B] = Kleisli[Future, A, B]
type HttpService = Service[Request, Response]
//Future[Either[A, B]]
type DecodeResult[T] =
EitherT[Future, DecodeFailure, T]
}
74. object Service {
def lift[A,B](f: A => Future[B]): Service[A,B] = Kleisli(f)
}
object HttpService {
def apply(f: PartialFunction[Request, Response]):
HttpService = Service.lift(liftToAsync(f))
def liftToAsync[A,B](f: A => B): A => Future[B] =
(a: A) => Future(f(a))
}
75. val httpService = HttpService {
case r1 @ Request(Get, "/") => Response(Ok)
case r2 @ Request(Post, "/") = Response(NotFound)
}
Http.runService(httpService)
Server
76. Client
We can reuse the HttpService type
val httpClient: HttpService = ???
val jsonResponseFromPipeline = httpService.map(_.body[Json])
val jsonFut: Future[DecodeResul[Json]] =
jsonResponseFromPipeline(Request(Get,"/"))
77. class AHClientWrapper(realClient: AHClient)
extends Request => Future[Response] {
def apply(req: Request): Future[Response] = {
//call realClient and return response
}
}
val httpClient: HttpService =
Kleisli(new AHClientWrapper(new AHClient))
Client
81. Takeaways
• This stuff is hard
• You must want to learn it
• There is no other way as building your knowledge from the ground up
• Approach it without being biased - this is just a tool
• It will help you understand/read/write high-level scala code
• Not everyone will appreciate that style of coding and that’s fine.