Monads and friends demystified

Monads and friends demystified

2c14a9713460e05479a7ce756078abea?s=128

Alessandro Lacava

March 24, 2016
Tweet

Transcript

  1. 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 }
  2. 4.

    Type Classes 102 object Show { // Alternative syntax def

    show[A: Show](a: A): String = implicitly[Show[A]].show(a) }
  3. 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) }
  4. 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[_]], ...
  5. 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) } }
  6. 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) } }
  7. 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))
  8. 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 )
  9. 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.
  10. 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.
  11. 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)
  12. 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))
  13. 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.
  14. 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)
  15. 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
  16. 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.
  17. 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) } }
  18. 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!
  19. 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
  20. 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?
  21. 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!
  22. 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.
  23. 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