Top Banner
Monads and friends demystified
25

Monads and friends demystified

Apr 14, 2017

Download

Software

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Monads and friends demystified

Monads and friends demystified

Page 2: Monads and friends demystified

About me

Blog: http://www.alessandrolacava.com

GitHub: https://github.com/lambdista

Twitter: https://twitter.com/lambdista

LinkedIn: https://www.linkedin.com/in/alessandrolacava

Page 3: Monads and friends demystified

Type Classes 101// 1. Capture a concepttrait 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 conceptimplicit val intShow = new Show[Int] { override def show(a: Int): String = a.toString}

Page 4: Monads and friends demystified

Type Classes 102

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

Page 5: Monads and friends demystified

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

Page 6: Monads and friends demystified

Higher-kinded typesKinds in Type Theory

Symbol Kind Examples* Simple type. AKA nullary

type constructor or proper type.

Int, String, Double, ...

* -> * Unary type constructor. List, Option, Set, ...

* -> * -> * Binary type constructor. Either, Function1, Map, ...

(* -> *) -> * Higher-order type operator, higher-kinded type for friends.

Foo[F[_]], Bar[G[_]], Functor[F[_]], Monad[M[_]], ...

Page 7: Monads and friends demystified

Example: MappableCapturing 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) }}

Page 8: Monads and friends demystified

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

Page 9: Monads and friends demystified

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

Page 10: Monads and friends demystified

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)

Page 11: Monads and friends demystified

Composition Law

map(f compose g)(fa) = map(f)(map(g)(fa))

E.g.: Option[A]

val f: Int => Int = x => x + 42val 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.

Page 12: Monads and friends demystified

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.

Page 13: Monads and friends demystified

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

Page 14: Monads and friends demystified

Enter The Applicative FunctorCapturing 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))

Page 15: Monads and friends demystified

Applicative instancesobject 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.

Page 16: Monads and friends demystified

Case Study Solvedimport 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)

Page 17: Monads and friends demystified

And now the dreadful MonadIf C is a Category a monad on C consists of an endofunctor T: C -> C together with two natural transformations: η: 1C -> T (where 1C 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 = 1T (as natural transformations T -> T; here 1T denotes the identity transformation from T to T).

Just Kidding!Well, not really. That’s the Wikipedia definition

Page 18: Monads and friends demystified

Monad for developerstrait 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.

Page 19: Monads and friends demystified

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

Page 20: Monads and friends demystified

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!

Page 21: Monads and friends demystified

CatsGitHub: 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

Page 22: Monads and friends demystified

Cats (0.4.0): Exampleimport 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?

Page 23: Monads and friends demystified

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!

Page 24: Monads and friends demystified

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.

Page 25: Monads and friends demystified

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