650

# A practical introduction to Category Theory - Voxxed Days Zurich 2017

Category Theory has become one of the hot topics in our community. Why is this theory suddenly so interesting for developers? Why are the cool kids talking so much about it? How can we apply its principles in our code? This talk will introduce the general principles behind Category Theory, it will show practical examples of how this theory has managed to simplify and solve common challenges that we encounter in our code daily.

## Daniela Sfregola

February 23, 2017

## Transcript

2017
2. ### HELLOOOOO > ex Java Developer > OOP background > I

am not a mathematician !

5. ### OBJECT ORIENTED PROGRAMMING (OOP) > composition of objects > abstraction

over the behaviour of each object
6. ### FUNCTIONAL PROGRAMMING (FP) > composition of functions > abstraction over

mathematical laws

13. ### LET'S BAKE SOME PIZZA! 1) get the dough 2) get

the sauce 3) get the cheese 3) cook in the oven until golden
14. ### OOP APPROACH Ingredient dough = getDough() Ingredient sauce = getSauce()

Ingredient cheese = getCheese() def cook(ingredients: List[Ingredient]): Pizza = ??? if (dough != null && sauce != null && cheese != null) cook(dough, sauce, cheese) else EXPLODE
15. ### MONADIC APPROACH def getDough(): Option[Ingredient] = ??? def getSauce(): Option[Ingredient]

= ??? def getCheese(): Option[Ingredient] = ??? def cook(ingredients: List[Ingredient]): Pizza = ??? for { dough <- getDough() sauce <- getSauce() cheese <- getCheese() } yield cook(dough, sauce, cheese)
16. ### DIFFERENT TYPES OF MONADS > Option > Try > Future

> ... > ...Yours here!
17. ### DATA VALIDATION > In almost every application > Can be

come complex quite quickly > Needs to be maintained

20. ### MAP scala> Some("daniela").map(s => "yo " + s) res0: Option[String]

= Some(yo daniela) scala> None.map(s => "yo " + s) res1: Option[String] = None
21. ### FLATMAP map + flatten scala> Some("daniela").flatMap(s => Some("yo " +

s)) res2: Option[String] = Some(yo daniela) scala> None.flatMap(s => Some("yo " + s)) res3: Option[String] = None
22. ### FOR-COMPREHENSION map + flatMap + (filter) scala > for {

a <- Some(1); b <- Some(5) } yield a + b res4: Option[Int] = Some(6) scala > for { a <- Some(1); b <- None } yield a + b res5: Option[Int] = None
23. ### OPTION package scala sealed abstract class Option[A] final case class

Some[A](x: A) extends Option[A] case object None extends Option[Nothing] def map[B](f: A => B): Option[B] = ??? def flatMap[B](f: A => Option[B]): Option[B] = ???
24. ### OPTION case class Data(email: String, phone: String) def validateEmail(e: String):

Option[String] = ??? def validatePhone(p: String): Option[String] = ??? def validateData(d: Data): Option[Data] = for { validEmail <- validateEmail(d.email) validPhone <- validatePhone(d.phone) } yield new Data(validEmail, validPhone)
25. ### OPTION val okEmail = "email@email.com"; val badEmail = "email.com" val

okPhone = "+1 1234567890123"; val badPhone = "not-a-valid-phone" > validateData(Data(okEmail, okPhone)) res0: Option[Data] = Some(Data(email@email.com,+1 1234567890123)) > validateData(Data(badEmail, badPhone)) res1: Option[Data] = None > validateData(Data(okEmail, badPhone)) res2: Option[Data] = None > validateData(Data(badEmail, okPhone)) res3: Option[Data] = None

ONE?
27. ### EITHER package scala.util sealed abstract class Either[A, B] final case

class Left[A](a: A) extends Either[A, B] final case class Right[B](b: B) extends Either[A, B] def map[D](f: B => D): Either[A, D] = ??? def flatMap[AA >: A, D](f: B => Either[AA, D]): Either[AA, D] = ???
28. ### EITHER case class Data(email: String, phone: String) def validateEmail(e: String):

Either[List[String], String] = ??? def validatePhone(p: String): Either[List[String], String] = ??? def validateData(d: Data): Either[List[String], Data] = for { validEmail <- validateEmail(d.email) validPhone <- validatePhone(d.phone) } yield new Data(validEmail, validPhone)
29. ### EITHER val okEmail = "email@email.com"; val badEmail = "email.com" val

okPhone = "+1 1234567890123"; val badPhone = "not-a-valid-phone" > validateData(Data(okEmail, okPhone)) res0: Either[List[String],Data] = Right(Data(email@email.com,+1 1234567890123)) > validateData(Data(badEmail, badPhone)) res1: Either[List[String],Data] = Left(List("Invalid email format")) > validateData(Data(okEmail, badPhone)) res2: Either[List[String],Data] = Left(List("Phone number must be numeric")) > validateData(Data(badEmail, okPhone)) res3: Either[List[String],Data] = Left(List("Invalid email format"))
30. ### EITHER > only one validation is performed > ideal only

when error accumulation is not needed
31. ### EITHER case class Data(email: String, phone: String) def validateEmail(e: String):

Either[List[String], String] = ??? def validatePhone(p: String): Either[List[String], String] = ??? def validateData(d: Data): Either[List[String], Data] = { val validEmail = validateEmail(d.email) val validPhone = validatePhone(d.phone) (validEmail, validPhone) match { case (Right(e), Right(p)) => Right(new Data(e, p)) case (Left(errE), Left(errP)) => Left(errE ++ errP) case (Left(errE), _) => Left(errE) case (_, Left(errP)) => Left(errP) } }
32. ### EITHER val okEmail = "email@email.com"; val badEmail = "email.com" val

okPhone = "+1 1234567890123"; val badPhone = "not-a-valid-phone" > validateData(Data(okEmail, okPhone)) res0: Either[List[String],Data] = Right(Data(email@email.com,+1 1234567890123)) > validateData(Data(badEmail, badPhone)) res1: Either[List[String],Data] = Left(List("Invalid email format", "Phone number must be numeric")) > validateData(Data(okEmail, badPhone)) res2: Either[List[String],Data] = Left(List("Phone number must be numeric")) > validateData(Data(badEmail, okPhone)) res3: Either[List[String],Data] = Left(List("Invalid email format"))
33. ### EITHER Which one is the error? Which one is the

valid value? Combine Either instances is not always easy or maintainable

35. ### VALIDATED package cats.data sealed abstract class Validated[+E, +A] final case

class Valid[+A](a: A) extends Validated[Nothing, A] final case class Invalid[+E](e: E) extends Validated[E, Nothing] def map[B](f: A => B): Validated[E,B] // no flatmap //...but we have something else *really* useful!
36. ### VALIDATED AND APPLY* import cats.Apply import cats.data.Validated import cats.implicits._ def

accumulate[E, A1, A2, B](v1: Validated[E, A1], v2: Validated[E, A2]) (f: (A1, A2) => B): Validated[E, B] = (v1 |@| v2).map(f) // same as: Apply[Validated[E, ?]].map2(v1,v2)(f) * More info on Apply at http://typelevel.org/cats/typeclasses/apply.html
37. ### VALIDATED import cats.implicits._ import cats.data.Validated case class Data(email: String, phone:

String) def validateEmail(e: String): Validated[List[String], String] = ??? def validatePhone(p: String): Validated[List[String], String] = ??? def validateData(d: Data): Validated[List[String], Data] = { val validEmail = validateEmail(d.email) val validPhone = validatePhone(d.phone) (validEmail |@| validPhone).map(Data) }
38. ### VALIDATED val okEmail = "email@email.com"; val badEmail = "email.com" val

okPhone = "+1 1234567890123"; val badPhone = "not-a-valid-phone" > validateData(Data(okEmail, okPhone)) res0: cats.data.Validated[List[String],Data] = Valid(Data(email@email.com,+1 1234567890123)) > validateData(Data(badEmail, badPhone)) res1: cats.data.Validated[List[String],Data] = Invalid(List("Invalid email format", "Phone number must be numeric")) > validateData(Data(okEmail, badPhone)) res2: cats.data.Validated[List[String],Data] = Invalid(List("Phone number must be numeric")) > validateData(Data(badEmail, okPhone)) res3: cats.data.Validated[List[String],Data] = Invalid(List("Invalid email format"))
39. ### SUMMARY > Category Theory: how things compose > Monads: good

for concatenation > Applicative Functors: good for validation

41. ### WANNA KNOW MORE? > Video: Category Theory for the Working

Hacker by @PhilipWadler > Video: Category Theory by @BartoszMilewski
42. ### THANK YOU! > Code on github: github.com/DanielaSfregola/data-validation > Twitter: @DanielaSfregola

> Blog: danielasfregola.com