The Functor, Applicative, Monad talk

The Functor, Applicative, Monad talk

Functors, applicatives, and monads are fundamental tools for some programmers, yet for many others they are considered immaterial. Indeed there are extremely few languages which offer support for even talking about these concepts. Why then are these programmers so fixated on them? What about them makes them so desirable and necessary? In this talk we will explore the what and why of these concepts and hopefully leave you understanding, if not convinced of, their utility.

Fb8e986500c5059b2a6c0b2184bb0faf?s=128

Adelbert Chang

November 16, 2017
Tweet

Transcript

  1. The Functor, Applicative, Monad talk Adelbert Chang @adelbertchang Scale by

    the Bay 2017
  2. Assumptions We are interested in pure functional programming

  3. Assumptions We are interested in pure functional programming For all

    f : A → B and a : A, there exists a b : B such that f (a) = b
  4. Assumptions We are interested in pure functional programming For all

    f : A → B and a : A, there exists a b : B such that f (a) = b For all expressions f (x) we can safely replace
  5. Assumptions We are interested in pure functional programming For all

    f : A → B and a : A, there exists a b : B such that f (a) = b For all expressions f (x) we can safely replace
  6. Assumptions We are interested in pure functional programming For all

    f : A → B and a : A, there exists a b : B such that f (a) = b For all expressions f (x) we can safely replace g(f(x), f(x))
  7. Assumptions We are interested in pure functional programming For all

    f : A → B and a : A, there exists a b : B such that f (a) = b For all expressions f (x) we can safely replace g(f(x), f(x)) val e = f(x) g(e, e)
  8. Implications No null or exception throwing

  9. Implications No null or exception throwing No mutation, print statements,

    or side effects in general
  10. Implications No null or exception throwing No mutation, print statements,

    or side effects in general
  11. Implications No null or exception throwing No mutation, print statements,

    or side effects in general var x = 0 x += 1 x += 1
  12. Implications No null or exception throwing No mutation, print statements,

    or side effects in general var x = 0 x += 1 x += 1 var x = 0 val e = x += 1 e e
  13. Getting back what we lost: null

  14. Getting back what we lost: null The concept of null

    is useful, an absence of a value
  15. Getting back what we lost: null The concept of null

    is useful, an absence of a value Instead of having an universal sentinel value that inhabits every type, make it data just like everything else
  16. Getting back what we lost: null The concept of null

    is useful, an absence of a value Instead of having an universal sentinel value that inhabits every type, make it data just like everything else
  17. Getting back what we lost: null The concept of null

    is useful, an absence of a value Instead of having an universal sentinel value that inhabits every type, make it data just like everything else sealed trait Option[A] final case class Some[A](value: A) extends Option[A] final case class None[A]() extends Option[A]
  18. Getting back what we lost: throwing exceptions Some functions we

    want to write will be partial in nature
  19. Getting back what we lost: throwing exceptions Some functions we

    want to write will be partial in nature Instead of giving A give Either[Error, A]
  20. Getting back what we lost: throwing exceptions Some functions we

    want to write will be partial in nature Instead of giving A give Either[Error, A]
  21. Getting back what we lost: throwing exceptions Some functions we

    want to write will be partial in nature Instead of giving A give Either[Error, A] sealed trait Either[E, A] final case class Left[E, A] (e: E) extends Either[E, A] final case class Right[E, A](a: A) extends Either[E, A]
  22. Getting back what we lost: mutation Many computations need state

    they can read and write to
  23. Getting back what we lost: mutation Many computations need state

    they can read and write to View the computation as a function from start state to the computed value and the new state
  24. Getting back what we lost: mutation Many computations need state

    they can read and write to View the computation as a function from start state to the computed value and the new state
  25. Getting back what we lost: mutation Many computations need state

    they can read and write to View the computation as a function from start state to the computed value and the new state final case class State[S, A](run: S => (A, S))
  26. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type
  27. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type
  28. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type def foo(a: Foo): Option[Bar] def bar(a: Foo): Either[Error, Bar] def baz(a: Foo): State[Baz, Bar] def baz(a: Foo, b: Baz): (Bar, Baz)
  29. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type def foo(a: Foo): Option[Bar] def bar(a: Foo): Either[Error, Bar] def baz(a: Foo): State[Baz, Bar] def baz(a: Foo, b: Baz): (Bar, Baz) Often referred to as effectful functions
  30. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type def foo(a: Foo): Option[Bar] def bar(a: Foo): Either[Error, Bar] def baz(a: Foo): State[Baz, Bar] def baz(a: Foo, b: Baz): (Bar, Baz) Often referred to as effectful functions Option, Either, State etc. are effects
  31. A pattern emerges For functions f : A → B

    that “do something” besides returning a B, augment the return type def foo(a: Foo): Option[Bar] def bar(a: Foo): Either[Error, Bar] def baz(a: Foo): State[Baz, Bar] def baz(a: Foo, b: Baz): (Bar, Baz) Often referred to as effectful functions Option, Either, State etc. are effects Values without these behaviors are pure
  32. Working with effects val x: Option[Int] = parseInt(...) val y

    = x match { case Some(int) => int max 0 case None => ??? }
  33. Working with effects val x: Option[Int] = parseInt(...) val y:

    Option[Int] = x match { case Some(int) => Some(int max 0) case None => None }
  34. Working with effects val x: Either[String, Host] = getKey[Host]("host") val

    y = x match { case Right(host) => host / "api" case Left(err) => ??? }
  35. Working with effects val x: Either[String, Host] = getKey[Host]("host") val

    y: Either[String, Endpoint] = x match { case Right(host) => Right(host / "api") case Left(err) => Left(err) }
  36. Working with effects val x: State[Long, Int] = randomInt val

    y: State[Long, Double] = State { (seed: Long) => ??? }
  37. Working with effects val x: State[Long, Int] = randomInt val

    y: State[Long, Double] = State { (seed: Long) => val (randomInt, newSeed) = x.run(input) ??? }
  38. Working with effects val x: State[Long, Int] = randomInt val

    y: State[Long, Double] = State { (seed: Long) => val (randomInt, newSeed) = x.run(input) (1.0 / randomInt, newSeed) }
  39. Working with effects Apply a pure function f : A

    → B to an effectful value F[A]
  40. Working with effects Apply a pure function f : A

    → B to an effectful value F[A] “Inside” F[A] we apply the function to the value and propagate some extra bits through, giving F[B]
  41. Working with effects Apply a pure function f : A

    → B to an effectful value F[A] “Inside” F[A] we apply the function to the value and propagate some extra bits through, giving F[B] These “extra bits” tend to be the “something” effectful functions do
  42. Functor trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A =>

    B): F[B] }
  43. Functor new Functor[Option] { def map[A, B](fa: Option[A])(f: A =>

    B): Option[B] = fa match { case Some(a) => Some(f(a)) case None => None } }
  44. new Functor[Either[E, ?]] { def map[A, B](fa: Either[E, A])(f: A

    => B): Either[E, B] fa match { case Right(a) => Right(f(a)) case Left(e) => Left(e) } } 1 1? syntax is enabled by the kind-projector compiler plugin https://github.com/non/kind-projector
  45. new Functor[State[S, ?]] { def map[A, B](fa: State[S, A])(f: A

    => B): State[S, B] = State { currentState => val (a, nextState) = fa.run(currentState) (f(a), nextState) } }
  46. val i: Option[Int] = parseInt(...) val bounded: Option[Int] = i.map(_

    max 0) val path: Either[String, Host] = getKey[Host]("host") val api: Either[String, Endpoint] = path.map(_ / "api") val rint: State[Long, Int] = randomInt val rdouble: State[Long, Double] = rint.map(i => 1.0 / i)
  47. Working with a single effectful value What if we want

    to work with two or more effectful values?
  48. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values
  49. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values Focus on tupling the values - (F[A], F[B]) => F[(A, B)]
  50. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values Focus on tupling the values - (F[A], F[B]) => F[(A, B)]
  51. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values Focus on tupling the values - (F[A], F[B]) => F[(A, B)] val fa: F[A] = ... val fb: F[B] = ... val fab: F[(A, B)] = ???
  52. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values Focus on tupling the values - (F[A], F[B]) => F[(A, B)] val fa: F[A] = ... val fb: F[B] = ... val fab: F[(A, B)] = fa.map(a => ???)
  53. Working with a single effectful value What if we want

    to work with two or more effectful values? Apply a pure n-ary function to n effectful values Focus on tupling the values - (F[A], F[B]) => F[(A, B)] val fa: F[A] = ... val fb: F[B] = ... val fab: F[(A, B)] = fa.map(a => fb.map(b => (a, b)) // ^ F[F[(A, B)]]
  54. Working with multiple effectful values

  55. Working with multiple effectful values def pairOption[A, B] (oa: Option[A],

    ob: Option[B]): Option[(A, B)] = (oa, ob) match { case (Some(a), Some(b)) => Some((a, b)) case _ => None }
  56. Working with multiple effectful values def pairEither[E, A, B] (ea:

    Either[E, A], eb: Either[E, B]): Either[E, (A, B)] = (ea, eb) match { case (Right(a), Right(b)) => Right((a, b)) case (Left(e), _) => Left(e) case (_, Left(e)) => Left(e) }
  57. Working with multiple effectful values def pairState[S, A, B] (sa:

    State[S, A], sb: State[S, B]): State[S, (A, B)] = State { currentState => ??? }
  58. Working with multiple effectful values def pairState[S, A, B] (sa:

    State[S, A], sb: State[S, B]): State[S, (A, B)] = State { currentState => val (a, nextState) = sa.run(currentState) ??? }
  59. Working with multiple effectful values def pairState[S, A, B] (sa:

    State[S, A], sb: State[S, B]): State[S, (A, B)] = State { currentState => val (a, nextState) = sa.run(currentState) val (b, finalState) = sb.run(nextState) ??? }
  60. Working with multiple effectful values def pairState[S, A, B] (sa:

    State[S, A], sb: State[S, B]): State[S, (A, B)] = State { currentState => val (a, nextState) = sa.run(currentState) val (b, finalState) = sb.run(nextState) ((a, b), finalState) }
  61. Applicative trait Applicative[F[_]] extends Functor[F] { def zip[A, B](fa: F[A],

    fb: F[B]): F[(A, B)] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] }
  62. new Applicative[Option] { def zip[A, B] (fa: Option[A], fb: Option[B]):

    Option[(A, B)] = (fa, fb) match { case (Some(a), Some(b)) => Some((a, b)) case _ => None } def pure[A](a: A): Option[A] = Some(a) ... }
  63. new Applicative[Either[E, ?]] { def zip[A, B] (fa: Either[E, A],

    fb: Either[E, B]): Either[E, (A, B)] (fa, fb) match { case (Right(a), Right(b)) => Right((a, b)) case (Left(e), _) => Left(e) case (_, Left(e)) => Left(e) } def pure[A](a: A): Either[E, A] = Right(a) ... }
  64. new Applicative[State[S, ?]] { def zip[A, B] (fa: State[S, A],

    fb: State[S, B]): State[S, (A, B)] = State { currentState => val (a, nextState) = fa.run(currentState) val (b, finalState) = fb.run(nextState) ((a, b), finalState) } def pure[A](a: A): State[S, A] = State(s => (a, s)) ... }
  65. Applicative trait Applicative[F[_]] extends Functor[F] { def zip[A, B](fa: F[A],

    fb: F[B]): F[(A, B)] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] def ap[A, B, C](ff: F[A => B])(fa: F[A]): F[B] = map(zip(ff, fa)) { case (f, a) => f(a) } }
  66. // Option[Int] parseInt(...).zip(parseInt(...)).map { case (x, y) => x max

    y } // Either[Error, Endpoint] getKey[Host]("host").zip(getKey[Port]("port")).map { case (host, p) => host :| p / "api" } // State[Long, Int] randomInt.zip(randomInt).map { case (x, y) => gcd(x, y) }
  67. None
  68. def traverseOption[A, B] (as: List[A])(f: A => Option[B]): Option[List[B]] =

    as.foldRight(Option(List.empty[B])) { (a, bs) => (f(a), bs) match { case (Some(h), Some(t)) => Some(h :: t) case _ => None } }
  69. def traverseEither[E, A, B] (as: List[A])(f: A => Either[E, B]):

    Either[E, List[B]] = as.foldRight(Either.right(List.empty[B])) { (a, bs) => (f(a), bs) match { case (Right(h), Right(t)) => Right(h :: t) case (Left(e), _) => Left(e) case (_, Left(e)) => Left(e) } }
  70. def traverseList[F[_]: Applicative, A, B] (as: List[A])(f: A => F[B]):

    F[List[B]] = as.foldRight(pure[F](List.empty[B])) { (a, bs) => f(a).zip(bs).map { case (h, t) => h :: t } }
  71. // traverseList[Option, A, B] def traverseOption[A, B] (as: List[A])(f: A

    => Option[B]): Option[List[B]] // traverseList[Either[E, ?], A, B] def traverseEither[A, B] (as: List[A])(f: A => Either[E, B]): Either[E, List[B]] // traverseList[State[S, ?], A, B] def traverseState[A, B] (as: List[A])(f: A => State[S, B]): State[S, List[B]]
  72. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values
  73. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front
  74. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front What if an F[B] is dependent on the value of an F[A]?
  75. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front What if an F[B] is dependent on the value of an F[A]?
  76. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front What if an F[B] is dependent on the value of an F[A]? def nextStep(x: Foo): F[Bar] val foo: F[Foo] = ... val bar: F[Bar] = ???
  77. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front What if an F[B] is dependent on the “result” of an F[A]? def nextStep(x: Foo): F[Bar] val foo: F[Foo] = ... val bar: F[Bar] = foo.map(x => ???)
  78. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front What if an F[B] is dependent on the “result” of an F[A]? def nextStep(x: Foo): F[Bar] val foo: F[Foo] = ... val bar: F[Bar] = foo.map(x => nextStep(x)) // ^ F[F[Bar]]
  79. Working with multiple dependent effectful values

  80. Working with multiple dependent effectful values def flatMapOption[A, B] (oa:

    Option[A])(f: A => Option[B]): Option[B] = oa match { case None => None case Some(a) => f(a) }
  81. Working with multiple dependent effectful values def flatMapEither[E, A, B]

    (ea: Either[E, A])(f: A => Either[E, B]): Either[E, B] = ea match { case Left(e) => Left(e) case Right(a) => f(a) }
  82. Working with multiple dependent effectful values def flatMapState[S, A, B]

    (sa: State[S, A])(f: A => State[S, B]): State[S, B] = State { currentState => ??? }
  83. Working with multiple dependent effectful values def flatMapState[S, A, B]

    (sa: State[S, A])(f: A => State[S, B]): State[S, B] = State { currentState => val (a, nextState) = sa.run(currentState) ??? }
  84. Working with multiple dependent effectful values def flatMapState[S, A, B]

    (sa: State[S, A])(f: A => State[S, B]): State[S, B] = State { currentState => val (a, nextState) = sa.run(currentState) val nextComputation = f(a) ??? }
  85. Working with multiple dependent effectful values def flatMapState[S, A, B]

    (sa: State[S, A])(f: A => State[S, B]): State[S, B] = State { currentState => val (a, nextState) = sa.run(currentState) val nextComputation = f(a) nextComputation.run(nextState) }
  86. Monad trait Monad[F[_]] extends Applicative[F] { def flatMap[A, B](fa: F[A])(f:

    A => F[B]): F[B] def pure[A](a: A): F[A] }
  87. Monad trait Monad[F[_]] extends Applicative[F] { def flatMap[A, B](fa: F[A])(f:

    A => F[B]): F[B] def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))) def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] = flatMap(fa)(a => map(fb)(b => (a, b))) }
  88. // Option[Data] parseHeader(...).flatMap(header => parseBody(header, ...)) // Either[Error, DeploymentUnit] getKey[String]("type").flatMap

    { value => if (value == "service") getKey[Service]("docker") else getKey[Job]("job") } // State[Long, Int] randomInt.flatMap(i => boundedRandomInt(i))
  89. Juggling effects We now have a vocabulary for working with

    effects
  90. Juggling effects We now have a vocabulary for working with

    effects Functors: a single effect
  91. Juggling effects We now have a vocabulary for working with

    effects Functors: a single effect Applicatives: multiple independent effects
  92. Juggling effects We now have a vocabulary for working with

    effects Functors: a single effect Applicatives: multiple independent effects Monads: multiple dependent effects
  93. username.flatMap { user => password.flatMap { pass => authenticate(user, pass).flatMap

    { token => newsFeed(token).zip(friendsList(token)).map { case (feed, friends) => render(feed, friends) } } } }
  94. for { user <- username pass <- password token <-

    authenticate(user, pass) data <- newsFeed(token).zip(friendsList(token)) (feed, friends) = data } yield render(feed, friends)
  95. What we didn’t have time to cover Laws - what

    makes a “good” functor, applicative, monad?
  96. What we didn’t have time to cover Laws - what

    makes a “good” functor, applicative, monad? Computation with more than one effect
  97. What we didn’t have time to cover Laws - what

    makes a “good” functor, applicative, monad? Computation with more than one effect Tracking IO as an effect
  98. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris
  99. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano
  100. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano Fine-grained effects, finally tagless, Free monads
  101. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano Fine-grained effects, finally tagless, Free monads Functional programming beyond code
  102. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano Fine-grained effects, finally tagless, Free monads Functional programming beyond code Nix(OS): The purely functional Linux distribution and package manager
  103. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano Fine-grained effects, finally tagless, Free monads Functional programming beyond code Nix(OS): The purely functional Linux distribution and package manager Reproducible builds, immutable infrastructure, reproducible environments
  104. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano Fine-grained effects, finally tagless, Free monads Functional programming beyond code Nix(OS): The purely functional Linux distribution and package manager Reproducible builds, immutable infrastructure, reproducible environments “Nelson: Rigorous Deployment for a Functional World” - Saturday @ 9:50 with Tim Perrett
  105. EOF