Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Adelbert Chang

November 16, 2017
Tweet

More Decks by Adelbert Chang

Other Decks in Programming

Transcript

  1. 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
  2. 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
  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 For all expressions f (x) we can safely replace
  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 g(f(x), f(x))
  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 g(f(x), f(x)) val e = f(x) g(e, e)
  6. Implications No null or exception throwing No mutation, print statements,

    or side effects in general var x = 0 x += 1 x += 1
  7. 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
  8. Getting back what we lost: null The concept of null

    is useful, an absence of a value
  9. 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
  10. 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
  11. 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]
  12. 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]
  13. 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]
  14. 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]
  15. 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
  16. 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
  17. 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))
  18. A pattern emerges For functions f : A → B

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

    that “do something” besides returning a B, augment the return type
  20. 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)
  21. 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
  22. 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
  23. 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
  24. Working with effects val x: Option[Int] = parseInt(...) val y

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

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

    y = x match { case Right(host) => host / "api" case Left(err) => ??? }
  27. 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) }
  28. Working with effects val x: State[Long, Int] = randomInt val

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

    y: State[Long, Double] = State { (seed: Long) => val (randomInt, newSeed) = x.run(input) ??? }
  30. 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) }
  31. Working with effects Apply a pure function f : A

    → B to an effectful value F[A]
  32. 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]
  33. 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
  34. 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 } }
  35. 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
  36. 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) } }
  37. 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)
  38. Working with a single effectful value What if we want

    to work with two or more effectful values?
  39. 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
  40. 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)]
  41. 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)]
  42. 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)] = ???
  43. 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 => ???)
  44. 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)]]
  45. 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 }
  46. 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) }
  47. Working with multiple effectful values def pairState[S, A, B] (sa:

    State[S, A], sb: State[S, B]): State[S, (A, B)] = State { currentState => ??? }
  48. 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) ??? }
  49. 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) ??? }
  50. 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) }
  51. 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] }
  52. 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) ... }
  53. 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) ... }
  54. 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)) ... }
  55. 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) } }
  56. // 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) }
  57. 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 } }
  58. 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) } }
  59. 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 } }
  60. // 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]]
  61. Working with multiple dependent effectful values Applicative lets us work

    with independent effectful values F[A] and F[B] are given up front
  62. 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]?
  63. 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]?
  64. 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] = ???
  65. 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 => ???)
  66. 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]]
  67. 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) }
  68. 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) }
  69. 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 => ??? }
  70. 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) ??? }
  71. 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) ??? }
  72. 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) }
  73. 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))) }
  74. // 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))
  75. Juggling effects We now have a vocabulary for working with

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

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

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

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

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

    makes a “good” functor, applicative, monad? Computation with more than one effect
  81. 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
  82. Tip of the iceberg Tomorrow @ 11:40 “Functional Programming with

    Effects” with Rob Norris “Functional Programming in Scala” by Runar Bjarnason and Paul Chiusano
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. EOF