A practical introduction to Category Theory - Voxxed Days Zurich 2017

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.

E99b07644586e9e1723757bf8e34ea68?s=128

Daniela Sfregola

February 23, 2017
Tweet

Transcript

  1. 11.
  2. 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
  3. 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
  4. 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)
  5. 17.

    DATA VALIDATION > In almost every application > Can be

    come complex quite quickly > Needs to be maintained
  6. 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
  7. 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
  8. 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
  9. 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] = ???
  10. 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)
  11. 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
  12. 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] = ???
  13. 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)
  14. 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"))
  15. 30.

    EITHER > only one validation is performed > ideal only

    when error accumulation is not needed
  16. 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) } }
  17. 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"))
  18. 33.

    EITHER Which one is the error? Which one is the

    valid value? Combine Either instances is not always easy or maintainable
  19. 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!
  20. 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
  21. 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) }
  22. 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"))
  23. 39.

    SUMMARY > Category Theory: how things compose > Monads: good

    for concatenation > Applicative Functors: good for validation
  24. 41.

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

    Hacker by @PhilipWadler > Video: Category Theory by @BartoszMilewski