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

Monads and friends demystified

Monads and friends demystified

Alessandro Lacava

March 24, 2016
Tweet

More Decks by Alessandro Lacava

Other Decks in Programming

Transcript

  1. Monads and friends demystified

  2. About me Blog: http://www.alessandrolacava.com GitHub: https://github.com/lambdista Twitter: https://twitter.com/lambdista LinkedIn: https://www.linkedin.com/in/alessandrolacava

  3. Type Classes 101 // 1. Capture a concept trait Show[A]

    { def show(a: A): String } object Show { // 2. Define a method that accept, implicitly, an instance of the concept def show[A](a: A)(implicit ev: Show[A]): String = ev.show(a) } // 3. Implement some instances of the concept implicit val intShow = new Show[Int] { override def show(a: Int): String = a.toString }
  4. Type Classes 102 object Show { // Alternative syntax def

    show[A: Show](a: A): String = implicitly[Show[A]].show(a) }
  5. Type Classes 103 object Show { // Commonly used pattern

    def apply[A: Show] = implicitly[Show[A]] // All methods using Show can now use this syntax def show[A: Show](a: A): String = Show[A].show(a) }
  6. Higher-kinded types Kinds in Type Theory Symbol Kind Examples *

    Simple type. AKA nullary type constructor or proper type. Int, String, Double, ... * -> * Unary type constructor. List, Option, Future, ... * -> * -> * Binary type constructor. Either, Function1, Map, ... (* -> *) -> * Higher-order type operator, higher-kinded type for friends. Foo[F[_]], Bar[G[_]], Functor[F[_]], Monad[M[_]], ...
  7. Example: Mappable Capturing the concept represented by the map method

    (See: Option, …): trait Mappable[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } object Mappable { def apply[F[_]: Mappable]: Mappable[F] = implicitly[Mappable[F]] def map[F[_]: Mappable, A, B](ma: F[A])(f: A => B): F[B] = Mappable[F].map(ma)(f) implicit val optionMapper = new Mappaple[Option] { override def map[A, B](oa: Option[A])(f: A => B): Option[B] = oa.map(f) } implicit val listMapper = new Mappaple[List] { override def map[A, B](la: List[A])(f: A => B): List[B] = la.map(f) } }
  8. Functor: Change the Mappable name... trait Functor[F[_]] { def map[A,

    B](fa: F[A])(f: A => B): F[B] } object Functor { def apply[F[_]: Functor]: Functor[F] = implicitly[Functor[F]] def map[F[_]: Functor, A, B](ma: F[A])(f: A => B): F[B] = Functor[F].map(ma)(f) implicit val optionFunctor = new Functor[Option] { override def map[A, B](oa: Option[A])(f: A => B): Option[B] = oa.map(f) } implicit val listFunctor = new Functor[List] { override def map[A, B](la: List[A])(f: A => B): List[B] = la.map(f) } }
  9. ...and add a couple of laws In order for something

    to be a functor, it should satisfy a couple of laws: Given a functor fa: F[A] and the identity function defined as follows: def identity[A](a: A): A = a the following laws must hold (where the order of the args is swapped, the Haskell way): Identity Law: map(identity)(fa) = fa Composition Law: map(f compose g)(fa) = map(f)(map(g)(fa))
  10. Identity Law map(identity)(fa) = fa => map(identity) = identity E.g.:

    Option[A] import Functor._ val some: Option[Int] = Some(42) val none: Option[Int] = None assert(map(some)(identity) == some ) assert(map(none)(identity) == none )
  11. Composition Law map(f compose g)(fa) = map(f)(map(g)(fa)) E.g.: Option[A] val

    f: Int => Int = x => x + 42 val g: Int => Int = x => x * x assert(map(some)(f compose g) == map(map(some)(g))(f) ) assert(map(none)(f compose g) == map(map(none)(g))(f) ) Note: The compiler cannot enforce these laws. You need to ensure them yourself.
  12. Lessons Learned The Mappable concept was easier to get because

    the name didn’t scare you. Rule I: Don’t let the buzzwords scare you Everything gets easier if you look closely at the types. Rule II: Always follow the types.
  13. Case Study A Functor lets us apply a function to

    a value which is inside a context. Context examples: Option, List, Future. Now, what if the function you want to apply is within a context as well? E.g.: def interpret(str: String): Option[Int => Int] = str.toLowerCase match { case "incr" => Some(_ + 1) case "decr" => Some(_ - 1) case "square" => Some(x => x * x) case "halve" => Some(x => x / 2) case _ => None } val v: Option[Int] = Some(42)
  14. Enter The Applicative Functor Capturing the concept of a function

    within a context, that is F[A => B]: trait Applicative[F[_]] extends Functor[F] { def pure[A](a: A): F[A] def ap[A, B](fa: F[A])(fab: F[A => B]): F[B] // from applicative you get a functor for free override def map[A, B](fa: F[A])(fab: A => B): F[B] = } object Applicative { def apply[F[_]: Applicative]: Applicative[F] = implicitly[Applicative[F]] def pure[F[_]: Applicative, A](a: A): F[A] = Applicative[F].pure(a) def ap[F[_]: Applicative, A, B](fa: F[A])(fab: F[A => B]): F[B] = Applicative[F].ap(fa)(fab) // ... Applicative instances } ap(fa)(pure(fab))
  15. Applicative instances object Applicative { // ... implicit val optionApplicative

    = new Applicative[Option] { override def pure[A](a: A): Option[A] = Option(a) override def ap[A, B](fa: Option[A])(fab: Option[A => B]): Option[B] = for { a <- fa f <- fab } yield f(a) } implicit val listApplicative = new Applicative[List] { override def pure[A](a: A): List[A] = List(a) override def ap[A, B](fa: List[A])(fab: List[A => B]): List[B] = for { a <- fa f <- fab } yield f(a) } } The Applicative Functor needs some laws satisfied as well that we won’t cover here.
  16. Case Study Solved import Applicative._ def interpret(str: String): Option[Int =>

    Int] = str.toLowerCase match { case "incr" => Some(_ + 1) case "decr" => Some(_ - 1) case "square" => Some(x => x * x) case "halve" => Some(x => x / 2) case _ => None } val func: Option[Int => Int] = interpret("incr") val v: Option[Int] = Some(42) val result: Option[Int] = ap(v)(func)
  17. And now the dreadful Monad If C is a Category

    a monad on C consists of an endofunctor T: C -> C together with two natural transformations: η: 1 C -> T (where 1 C denotes the identity functor on C) μ: T2 -> T (where T2 is the functor T T from C to C). These are required to fulfill the following conditions (sometimes called coherence conditions): μ Tμ = μ μ T (as natural transformations T3 -> T); μ T η = μ η T = 1 T (as natural transformations T -> T; here 1 T denotes the identity transformation from T to T). Just Kidding! Well, not really. That’s the Wikipedia definition
  18. Monad for developers trait Monad[M[_]] extends Applicative[M] { def unit[A](a:

    A): M[A] def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] override def pure[A](a: A): M[A] = unit(a) override def ap[A, B](fa: M[A])(f: M[A => B]): M[B] = flatMap(fa)(a => map(f)(ff => ff(a))) } The primitives required by the Monad type class are just unit and flatMap or, equivalently, unit and join (along with map inherited from Functor): def join(mma: M[M[A]]): M[A] You can derive flatMap from unit, join and map.
  19. Monad continued... object Monad { def apply[M[_] : Monad]: Monad[M]

    = implicitly[Monad[M]] def unit[M[_]: Monad, A](a: A): M[A] = Monad[M].unit(a) def flatMap[M[_]: Monad, A, B](ma: M[A])(f: A => M[B]): M[B] = Monad[M].flatMap(ma)(f) implicit val optionMonad = new Monad[Option] { override def unit[A](a: A): Option[A] = Option(a) override def flatMap[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = ma.flatMap(f) } implicit val listMonad = new Monad[List] { override def unit[A](a: A): List[A] = List(a) override def flatMap[A, B](ma: List[A])(f: A => List[B]): List[B] = ma.flatMap(f) } }
  20. Monad Laws Do we need to write our own type

    classes and implementations for concepts such as Functor, Applicative and Monad? In Scala pseudocode: Left Identity: unit(a).flatMap(f) == f(a) Right Identity: m.flatMap(unit) == m Associativity: (m.flatMap(f)).flatMap(g) == m.flatMap(a => f(a).flatMap(g)) Not really!
  21. Cats GitHub: https://github.com/typelevel/cats libraryDependencies += "org.typelevel" %% "cats" % catsVersion

    Resources for Learners: http://typelevel.org/cats/ http://eed3si9n.com/herding-cats/ Cats is broken up into a number of sub-projects: • core - contains type class definitions (e.g. Functor, Applicative, Monad), essential datatypes, and type class instances for those datatypes and standard library types • laws - laws for the type classes, used to validate type class instances • tests - tests that check type class instances with laws from laws • docs - The source for this website
  22. Cats (0.4.0): Example import cats._ import cats.std.all._ val inc: Int

    => Int = _ + 1 val some: Option[Int] = Some(42) val none: Option[Int] = None val list: List[Int] = List(1, 2, 3) val emptyList: List[Int] = List() val optResult1 = Functor[Option].map(some)(inc) // Some(43) val optResult2 = Functor[Option].map(none)(inc) // None val listResult1 = Functor[List].map(list)(inc) // List(2, 3, 4) val listResult2 = Functor[List].map(emptyList)(inc) // List() Nice. But it would be more useful if we could abstract over Functor, wouldn’t it?
  23. Cats (0.4.0): Abstracting over Functor val inc: Int => Int

    = _ + 1 val some: Option[Int] = Some(42) val none: Option[Int] = None val list: List[Int] = List(1, 2, 3) val emptyList: List[Int] = List() def map[F[_]: Functor, A, B](fa: F[A])(f: A => B): F[B] = Functor[F].map(fa)(f) val optResult3 = map(some)(inc) // Some(43) val listResult3 = map(list)(inc) // List(2, 3, 4) Great. Now what if we want to compose two functors? No problem man!
  24. Cats (0.4.0): Functors compose! val inc: Int => Int =

    _ + 1 val listOfOpts = List(Some(1), None, Some(3)) // We want to map inc to listOfOpts val listOptFunctor = Functor[List] compose Functor[Option] val listOptResult = listOptFunctor.map(listOfOpts)(inc) // List(Some(2), None, Some(4)) Applicatives compose too. Monads do not, mechanically, compose. Some of them do and their composition is generally achieved through Monad transformers.
  25. Shameless Plug I dealt with these and other subjects in

    this book: Professional Scala - Wrox URL: http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119267226.html