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

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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  4. Type Classes 102
    object Show {
    // Alternative syntax
    def show[A: Show](a: A): String = implicitly[Show[A]].show(a)
    }

    View Slide

  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)
    }

    View Slide

  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[_]],
    ...

    View Slide

  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)
    }
    }

    View Slide

  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)
    }
    }

    View Slide

  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))

    View Slide

  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
    )

    View Slide

  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.

    View Slide

  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.

    View Slide

  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)

    View Slide

  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))

    View Slide

  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.

    View Slide

  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)

    View Slide

  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

    View Slide

  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.

    View Slide

  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)
    }
    }

    View Slide

  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!

    View Slide

  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

    View Slide

  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?

    View Slide

  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!

    View Slide

  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.

    View Slide

  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

    View Slide