monads and friends demystified
TRANSCRIPT
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
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}
Type Classes 102
object Show { // Alternative syntax def show[A: Show](a: A): String = implicitly[Show[A]].show(a)}
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)}
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[_]], ...
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) }}
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) }}
...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))
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)
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.
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.
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)
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))
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.
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)
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
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.
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) }}
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!
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
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?
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!
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.
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