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, Set, ...
* -> * -> * 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:
η: 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
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.
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