$30 off During Our Annual Pro Sale. View Details »

Lambda World 2017 - A Pragmatic Introduction to Category Theory

Lambda World 2017 - A Pragmatic Introduction to Category Theory

Category Theory has become one of the hot topics in the community. Why is this theory suddenly so interesting for developers? Why are the cool kids talking so much about it? This talk will introduce the general principles of Category Theory in a pragmatic, non-mathematical way. We will show practical examples of how this theory has managed to simplify and solve common challenges that we encounter in our code daily, such as nullable values, error handling, parallel and sequential operations and data validation. Also, we will apply them to create our own category theory library from scratch using Scala and ScalaCheck as the only dependency.

Daniela Sfregola

October 27, 2017
Tweet

More Decks by Daniela Sfregola

Other Decks in Programming

Transcript

  1. A PRAGMATIC
    INTRODUCTION TO
    CATEGORY THEORY
    @DANIELASFREGOLA
    LAMBDA WORLD 2017
    github.com/DanielaSfregola/tutorial-cat

    View Slide

  2. HELLOOOOO
    > ex Java Developer
    > OOP background
    > I am not a mathematician !

    View Slide

  3. I AM NOT A MATHEMATICIAN

    View Slide

  4. YOU DO NOT NEED TO KNOW
    CATEGORY THEORY
    TO WRITE
    SCALA CODE

    View Slide

  5. YOU DO NOT NEED TO KNOW
    CATEGORY THEORY
    TO WRITE
    FUNCTIONAL CODE

    View Slide

  6. CATEGORY THEORY
    DEEPER UNDERSTANDING ON
    OUR CODE

    View Slide

  7. HOW DO WE
    REASON ?

    View Slide

  8. COMPOSITION
    ABSTRACTION

    View Slide

  9. CATEGORY THEORY
    HOW THINGS COMPOSE

    View Slide

  10. ARROW THEORY
    CATEGORY THEORY
    HOW THINGS COMPOSE

    View Slide

  11. WHAT IS A CATEGORY?

    View Slide

  12. COMPOSITION LAW

    View Slide

  13. IDENTITY LAW

    View Slide

  14. COMPOSITION + ASSOCIATIVITY

    View Slide

  15. CATEGORY'S RULES
    > Identity
    > Composition
    > Associativity

    View Slide

  16. A PRACTICAL EXAMPLE

    View Slide

  17. CATEGORY WITH 1 OBJECT

    View Slide

  18. CATEGORY WITH 1 OBJECT
    =
    MONOID

    View Slide

  19. MONOID'S RULES
    Identity
    n o id == id o n == n
    Composition
    forall x, y => x o y
    Associativity
    x o (y o z) == (x o y) o z

    View Slide

  20. SCALACHECK FTW!
    MonoidSpec
    // n o id == id o n == n
    property("identity") = forAll { n: A =>
    monoid.compose(n, id) == n &&
    monoid.compose(id, n) == n
    }
    // forall x, y => x o y
    property("composition") = forAll { (x: A, y: A) =>
    monoid.compose(x, y).isInstanceOf[A]
    }
    // x o (y o z) == (x o y) o z
    property("associativity") = forAll { (x: A, y: A, z: A) =>
    val xY = monoid.compose(x,y)
    val yZ = monoid.compose(y,z)
    monoid.compose(xY, z) == monoid.compose(x, yZ)
    }

    View Slide

  21. A PRACTICAL EXAMPLE

    View Slide

  22. MONOID
    trait Monoid[A] {
    def identity: A
    def compose(x: A, y: A): A
    }

    View Slide

  23. MONOID INSTANCES (1)
    implicit val intMonoid: Monoid[Int] =
    new Monoid[Int] {
    def compose(x: Int, y: Int): Int = x + y
    def identity: Int = 0
    }

    View Slide

  24. MONOID INSTANCES (2)
    implicit val stringMonoid: Monoid[String] =
    new Monoid[String] {
    def compose(x: String, y: String): String = x + y
    def identity: String = ""
    }

    View Slide

  25. CATEGORY WITH 1+ OBJECT

    View Slide

  26. CATEGORY IN A BOX

    View Slide

  27. CATEGORY IN A BOX
    > Objects are in a Box
    > All the arrows are copied

    View Slide

  28. LIFTING: CONTEXT VS CONTENT

    View Slide

  29. EXAMPLE OF BOXES
    > Option
    > Future
    > Try
    > List
    > Either

    View Slide

  30. CATEGORY IN A BOX
    =
    FUNCTOR

    View Slide

  31. FUNCTOR'S RULES
    Identity
    map(id) == id
    Composition
    map(g o f) == map(g) o map(f)
    Associativity
    map(h o g) o map(f) == map(h) o map(g o f)

    View Slide

  32. SCALACHECK FTW!
    FunctorSpec
    // map_id == id
    property("identity") = forAll { box: Box[A] =>
    map(box)(identity) == box
    }
    // map_(g o f) == (map_g) o (map_f)
    property("composition") = forAll { boxA: Box[A] =>
    val fG = f andThen g
    val mapFG: Box[A] => Box[C] = map(_)(fG)
    mapFG(boxA) == (mapF andThen mapG)(boxA)
    }
    // map_(h o g) o map_f == map_h o map_(g o f)
    property("associativity") = forAll { boxA: Box[A] =>
    val fG = f andThen g
    val mapFG: Box[A] => Box[C] = map(_)(fG)
    val gH = g andThen h
    val mapGH: Box[B] => Box[D] = map(_)(gH)
    (mapF andThen mapGH)(boxA) == (mapFG andThen mapH)(boxA)
    }

    View Slide

  33. LIFTING: CONTEXT VS CONTENT

    View Slide

  34. FUNCTOR
    class Functor[Box[_]] {
    def map[A, B](boxA: Box[A])
    (f: A => B): Box[B]
    }

    View Slide

  35. MAYBE
    sealed abstract class Maybe[+A]
    final case class Just[A](a: A) extends Maybe[A]
    case object Empty extends Maybe[Nothing]

    View Slide

  36. FUNCTOR FOR MAYBE
    implicit val maybeFunctor: Functor[Maybe] =
    new Functor[Maybe] {
    override def map[A, B](boxA: Maybe[A])
    (f: A => B): Maybe[B] =
    boxA match {
    case Just(a) => Just(f(a))
    case Empty => Empty
    }
    }

    View Slide

  37. BOX FUNCTION + BOX VALUES

    View Slide

  38. COMBINE MORE BOXES
    INTO ONE
    =
    APPLICATIVE

    View Slide

  39. APPLICATIVE'S RULES
    > Identity
    > Composition
    > Associativity
    > Homorphism
    > Interchange
    ...and more!

    View Slide

  40. SCALACHECK FTW!
    ApplicativeSpec extends FunctorSpec
    // ap(id)(a) == a
    property("identity") = forAll { box: Box[A] =>
    ap(pureIdentity)(box) == box
    }
    // ap(pure(f))(pure(a)) == pure(f(a))
    property("homorphism") = forAll { a: A =>
    ap(pureF)(pure(a)) == pure(f(a))
    }
    // {x => pure(x)}(a) == pure(a)
    property("interchange") = forAll { a: A =>
    toPureA(a) == pure(a)
    }
    // pure(h o g o f) == ap(pure(h o g))(pure(f(a)))
    property("composition") = forAll { a: A =>
    val gH = g andThen h
    val fGH = f andThen gH
    val pureGH = pure(gH)
    val pureFA = pure(f(a))
    pure(fGH(a)) == ap(pureGH)(pureFA)
    }

    View Slide

  41. COMBINE BOXES TOGETHER
    > How to create a new box
    > How to combine their values together

    View Slide

  42. APPLICATIVE
    class Applicative[Box[_]] extends Functor[Box] {
    def pure[A](a: A): Box[A]
    def ap[A, B](boxF: Box[A => B])(boxA: Box[A]): Box[B]
    /*************/
    def map[A, B](boxA: Box[A])(f: A => B): Box[B] =
    ap[A, B](pure(f))(boxA)
    }

    View Slide

  43. BOX FUNCTION + BOX VALUES

    View Slide

  44. APPLICATIVE
    class Applicative[Box[_]] extends Functor[Box] {
    def pure[A](a: A): Box[A]
    def ap[A, B](boxF: Box[A => B])(value: Box[A]): Box[B]
    def ap2[A1, A2, B](boxF: Box[(A1, A2) => B])
    (value1: Box[A1], value2: Box[A2]): Box[B]
    // up to 22 values!
    // same for map
    }

    View Slide

  45. APPLICATIVE FOR MAYBE
    implicit val maybeApplicative: Applicative[Maybe] =
    new Applicative[Maybe] {
    def pure[A](a: A): Maybe[A] = Just(a)
    def ap[A, B](boxF: Maybe[A => B])(boxA: Maybe[A]): Maybe[B] =
    (boxF, boxA) match {
    case (Just(f), Just(a)) => pure(f(a))
    case _ => Empty
    }
    }

    View Slide

  46. BOX IN A BOX

    View Slide

  47. BOX IN A BOX

    View Slide

  48. FUSE TWO BOXES
    TOGETHER
    =
    MONAD

    View Slide

  49. MONAD'S RULES
    > Identity
    > Composition
    > Associativity

    View Slide

  50. SCALACHECK FTW!
    MonadSpec extends ApplicativeSpec
    // flatMap(pure(a))(f(a)) == f(a)
    property("left identity") = forAll { a: A =>
    flatMap(pure(a))(toPureFa) == toPureFa(a)
    }
    // flatMap(pure(a))(f(a)) == f(a)
    property("right identity") = forAll { a: A =>
    flatMap(toPureFa(a))(pure) == toPureFa(a)
    }
    // pure(h o g o f) == ap(pure(h o g))(pure(f(a)))
    property("associativity") = forAll { boxA: Box[A] =>
    val left: Box[C] = flatMap(flatMap(boxA)(toPureFa))(toPureGb)
    val right: Box[C] = flatMap(boxA)(a => flatMap(toPureFa(a))(toPureGb))
    left == right
    }

    View Slide

  51. MONAD (AS FUNCTOR)
    class Monad[Box[_]] extends Functor[Box] {
    // map from Functor
    def flatten[A](bb: Box[Box[A]]): Box[A]
    /*******/
    def flatMap[A, B](valueA: Box[A])
    (f: A => Box[B]): Box[B] = {
    val bb: Box[Box[B]] = map(valueA)(f)
    bb.flatten
    }
    }

    View Slide

  52. BOXES IN A SEQUENCE

    View Slide

  53. FOR-COMPREHENSION
    val boxA: Box[A]
    def toBoxB: A => Box[B]
    def toBoxC: B => Box[C]
    def toBoxD: C => Box[D]
    for {
    a <- boxA
    b <- toBoxB(a)
    c <- toBoxC(b)
    d <- toBoxD(c)
    } yield d

    View Slide

  54. MONAD (AS APPLICATIVE)
    trait Monad[Box[_]] extends Applicative[Box] {
    // pure from Applicative
    def flatMap[A, B](boxA: Box[A])(f: A => Box[B]): Box[B]
    /******/
    def flatten[A](boxBoxA: Box[Box[A]]): Box[A] =
    flatMap(boxBoxA)(identity)
    def ap[A, B](boxF: Box[A => B])(boxA: Box[A]): Box[B] =
    flatMap(boxF)(f => map(boxA)(f))
    def map[A, B](boxA: Box[A])(f: A => B): Box[B] =
    flatMap(boxA)(a => pure(f(a)))
    }

    View Slide

  55. MONAD FOR MAYBE
    implicit val maybeMonad: Monad[Maybe] = new Monad[Maybe] {
    def flatMap[A, B](boxA: Maybe[A])
    (f: (A) => Maybe[B]): Maybe[B] =
    boxA match {
    case Just(a) => f(a)
    case Empty => Empty
    }
    def pure[A](a: A): Maybe[A] =
    maybeApplicative.pure(a)
    }

    View Slide

  56. MONAD IS
    A MONOID
    IN THE CATEGORY OF
    ENDOFUNCTORS

    View Slide

  57. MONAD
    MONOID => pure + flatten
    ENDOFUNCTORS => map

    View Slide

  58. SUMMARY
    CATEGORY THEORY >> how things compose
    MONOID >> combining 2 values into 1
    FUNCTOR >> values lifted to a context
    APPLICATIVE >> independent values applied
    to a function in a context
    MONAD >> ops in sequence in a context

    View Slide

  59. Band
    BoundedSemilattice CommutativeGroup
    CommutativeMonoid
    CommutativeSemigroup
    Eq
    Group
    Semigroup
    Monoid
    Order
    PartialOrder
    Semilattice
    Alternative
    Applicative
    ApplicativeError
    Apply
    Bifoldable
    Bimonad
    Bitraverse
    Cartesian
    CoflatMap
    Comonad
    ContravariantCartesian
    FlatMap
    Foldable Functor
    Inject
    InvariantMonoidal
    Monad
    MonadError
    MonoidK
    NotNull
    Reducible
    SemigroupK
    Show
    ApplicativeAsk
    Bifunctor
    Contravariant
    Invariant
    Profunctor
    Strong
    Traverse
    Arrow
    Category
    Choice
    Compose
    Cats Type Classes
    kernel
    core/functor
    core/arrow
    core The highlighted type classes are the first ones
    you should learn. They’re well documented
    and well-known so it’s easy to get help. a |+| b
    a === b
    a =!= b
    a |@| b
    a *> b
    a <* b
    a <+> b
    a >>> b
    a <<< b
    a > b
    a >= b
    a < b
    a <= b
    Sync
    Async
    Effect
    LiftIO
    effect
    Some type classes introduce
    symbolic operators.
    NonEmptyTraverse
    InjectK
    CommutativeArrow
    CommutativeFlatMap
    CommutativeMonad
    ApplicativeLayer
    FunctorLayer
    ApplicativeLayerFunctor
    FunctorLayerFunctor
    ApplicativeLocal
    FunctorEmpty
    FunctorListen
    FunctorTell
    FunctorRaise
    MonadLayer
    MonadLayerFunctor
    MonadLayerControl
    MonadState
    TraverseEmpty
    Functor
    Applicative
    Monad
    Traverse
    mtl
    MTL type classes do not extend core type
    classes directly, but the effect is similar; the
    dashed line can be read “implies”.

    View Slide

  60. FORGET ABOUT THE DETAILS
    FOCUS ON
    HOW THINGS COMPOSE

    View Slide

  61. WANNA KNOW MORE?
    > Category Theory for the WH by @PhilipWadler
    > Category Theory by @BartoszMilewski
    > Cats-Infographics by Rob Norris - @tpolecat
    > Cats Documentation - Type Classes

    View Slide

  62. THANK YOU!
    > Twitter: @DanielaSfregola
    > Blog: danielasfregola.com
    github.com/DanielaSfregola/tutorial-cat

    View Slide